├── .rspec ├── sorbet ├── config ├── tapioca │ ├── require.rb │ └── config.yml └── rbi │ ├── known_constants.rbi │ └── gems │ ├── pry@0.14.1.rbi │ ├── coderay@1.1.3.rbi │ ├── unparser@0.6.5.rbi │ ├── method_source@1.0.0.rbi │ ├── rspec@3.11.0.rbi │ ├── netrc@0.11.0.rbi │ ├── parallel@1.22.1.rbi │ ├── yard-sorbet@0.7.0.rbi │ ├── ast@2.4.2.rbi │ ├── zeitwerk@2.6.0.rbi │ └── diff-lcs@1.5.0.rbi ├── lib ├── changeset │ ├── version.rb │ ├── persistence_interface.rb │ ├── errors.rb │ ├── null_event_catalog.rb │ ├── event_catalog_interface.rb │ ├── configuration.rb │ ├── async_changeset.rb │ ├── db_operation_collection.rb │ ├── event.rb │ └── event_collection.rb └── changeset.rb ├── .github └── workflows │ ├── lint.yml │ ├── sorbet.yml │ └── rspec.yml ├── changelog.md ├── changeset.gemspec ├── bin └── tapioca ├── Gemfile ├── MIT-LICENSE ├── LICENSE ├── .gitignore ├── spec ├── changeset │ ├── changeset_integration_spec.rb │ ├── merge_child_spec.rb │ ├── changeset_spec.rb │ └── merge_child_async_spec.rb └── spec_helper.rb ├── doc └── combo.svg ├── Gemfile.lock ├── rbi └── changeset.rbi └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /sorbet/config: -------------------------------------------------------------------------------- 1 | --dir 2 | . 3 | --ignore=vendor/ 4 | -------------------------------------------------------------------------------- /lib/changeset/version.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | class Changeset 4 | VERSION = "0.1.5" 5 | end 6 | -------------------------------------------------------------------------------- /lib/changeset/persistence_interface.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | class Changeset 4 | module PersistenceInterface 5 | def call 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /sorbet/tapioca/require.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | # frozen_string_literal: true 3 | 4 | # Add your extra requires here (`bin/tapioca require` can be used to boostrap this list) 5 | -------------------------------------------------------------------------------- /sorbet/rbi/known_constants.rbi: -------------------------------------------------------------------------------- 1 | # typed: false 2 | class URI::File; end 3 | class ::Spoom::ExecResult; end 4 | class ::Spoom::Sorbet::Config; end 5 | class ::Spoom::Sorbet::Errors::Error; end -------------------------------------------------------------------------------- /lib/changeset/errors.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | class Changeset 4 | module Errors 5 | class BaseError < StandardError; end 6 | 7 | class UnknownEventError < BaseError; end 8 | 9 | class MissingConfigurationError < BaseError; end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/changeset/null_event_catalog.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | class Changeset 4 | class NullEventCatalog 5 | def dispatch(event) 6 | raise "No events in NullEventCatalog" 7 | end 8 | 9 | def known_event?(event_name) 10 | false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /sorbet/tapioca/config.yml: -------------------------------------------------------------------------------- 1 | gem: 2 | # Add your `gem` command parameters here: 3 | # 4 | # exclude: 5 | # - gem_name 6 | # doc: true 7 | # workers: 5 8 | dsl: 9 | # Add your `dsl` command parameters here: 10 | # 11 | # exclude: 12 | # - SomeGeneratorName 13 | # workers: 5 14 | -------------------------------------------------------------------------------- /lib/changeset/event_catalog_interface.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | class Changeset 4 | module EventCatalogInterface 5 | def dispatch(event) 6 | end 7 | 8 | def known_event?(event_name) 9 | false 10 | end 11 | 12 | def class 13 | super 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/pry@0.14.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `pry` gem. 5 | # Please instead update this file by running `bin/tapioca gem pry`. 6 | 7 | # THIS IS AN EMPTY RBI FILE. 8 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 9 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/coderay@1.1.3.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `coderay` gem. 5 | # Please instead update this file by running `bin/tapioca gem coderay`. 6 | 7 | # THIS IS AN EMPTY RBI FILE. 8 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 9 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/unparser@0.6.5.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `unparser` gem. 5 | # Please instead update this file by running `bin/tapioca gem unparser`. 6 | 7 | # THIS IS AN EMPTY RBI FILE. 8 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 9 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/method_source@1.0.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `method_source` gem. 5 | # Please instead update this file by running `bin/tapioca gem method_source`. 6 | 7 | # THIS IS AN EMPTY RBI FILE. 8 | # see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem 9 | -------------------------------------------------------------------------------- /lib/changeset/configuration.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | class Changeset 4 | class Configuration 5 | attr_writer :db_transaction_wrapper 6 | 7 | def db_transaction_wrapper 8 | return @db_transaction_wrapper if @db_transaction_wrapper 9 | 10 | raise Changeset::Errors::MissingConfigurationError, "db_transaction_wrapper" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | tests: 10 | name: Sorbet 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 3.1 17 | - run: bundle install --jobs 4 --retry 3 18 | - run: bundle exec standardrb 19 | -------------------------------------------------------------------------------- /.github/workflows/sorbet.yml: -------------------------------------------------------------------------------- 1 | name: Sorbet 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | tests: 10 | name: Sorbet 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 3.1 17 | - run: bundle install --jobs 4 --retry 3 18 | - run: bundle exec srb tc 19 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 0.1.5 2 | - Make commit_db_operations and dispatch_events public 3 | 4 | # 0.1.4 5 | - Make DbOperationCollection an enumerable 6 | 7 | # 0.1.3 8 | - Add `merge_child_async` for legacy code concerns 9 | 10 | # 0.1.2 11 | - Breaking change: `add_event` signature without keyword args 12 | 13 | # 0.1.1 14 | - Now Db operations have to respond to `call` vs `commit`, opening the doors to simple lambdas. 15 | - Remove constraints on events payload types 16 | -------------------------------------------------------------------------------- /.github/workflows/rspec.yml: -------------------------------------------------------------------------------- 1 | name: Rspec 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | tests: 10 | name: all 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | ruby-version: ['3.2', '3.1', '3.0'] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: ${{ matrix.ruby-version }} 20 | - run: bundle install --jobs 4 --retry 3 21 | - run: bundle exec rspec spec 22 | -------------------------------------------------------------------------------- /changeset.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("lib", __dir__) 2 | 3 | require "changeset/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "changeset" 7 | spec.version = Changeset::VERSION 8 | spec.authors = ["Benjamin Roth"] 9 | spec.email = ["benjamin@rubyist.fr"] 10 | spec.summary = "Propagate persistence and events from actions" 11 | spec.description = "Propagate persistence and events from actions" 12 | spec.license = "MIT" 13 | 14 | spec.files = Dir["{lib,rbi}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 15 | 16 | spec.add_dependency "zeitwerk" 17 | end 18 | -------------------------------------------------------------------------------- /lib/changeset/async_changeset.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | class Changeset 4 | class AsyncChangeset 5 | class InconsistencyError < StandardError; end 6 | 7 | def initialize(changeset_wrapped_in_proc) 8 | @changeset_wrapped_in_proc = changeset_wrapped_in_proc 9 | @called = false 10 | end 11 | 12 | def db_operations 13 | changeset.send(:db_operations) 14 | end 15 | 16 | def events_collection 17 | changeset.send(:events_collection) 18 | end 19 | 20 | private 21 | 22 | def changeset 23 | @changeset ||= @changeset_wrapped_in_proc.call.tap do |result| 24 | raise InconsistencyError unless result.is_a?(::Changeset) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /bin/tapioca: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'tapioca' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("tapioca", "tapioca") 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # Declare your gem's dependencies in flow.gemspec. 5 | # Bundler will treat runtime dependencies like base dependencies, and 6 | # development dependencies will be added by default to the :development group. 7 | gemspec 8 | 9 | # Declare any dependencies that are still in development here instead of in 10 | # your gemspec. These might include edge Rails or gems from your path or 11 | # Git. Remember to move these dependencies to your gemspec before releasing 12 | # your gem to rubygems.org. 13 | 14 | # To use a debugger 15 | # gem 'byebug', group: [:development, :test] 16 | gem "sorbet-runtime", "0.5.9204" 17 | 18 | group :development, :test do 19 | gem "sorbet", "0.5.9204" 20 | gem "tapioca", require: false 21 | gem "standard" 22 | end 23 | 24 | group :test do 25 | gem "rspec", "3.11.0" 26 | gem "byebug" 27 | end 28 | -------------------------------------------------------------------------------- /lib/changeset/db_operation_collection.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | class Changeset 4 | class DbOperationCollection 5 | include Enumerable 6 | 7 | def initialize 8 | @collection = [] 9 | end 10 | 11 | def add(persistence_handler) 12 | collection.push(persistence_handler) 13 | end 14 | 15 | def merge_child(db_operations) 16 | db_operations.collection.each do |db_operation| 17 | add(db_operation) 18 | end 19 | end 20 | 21 | def merge_child_async(async_change_set) 22 | add(async_change_set) 23 | self 24 | end 25 | 26 | def each(&block) 27 | collection.each do |element| 28 | case element 29 | when Changeset::AsyncChangeset 30 | element.db_operations.each do |operation| 31 | yield(operation) 32 | end 33 | else 34 | yield(element) 35 | end 36 | end 37 | end 38 | 39 | def ==(other) 40 | collection == other.collection 41 | end 42 | 43 | protected 44 | 45 | attr_reader :collection 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Benjamin Roth 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Benjamin Roth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/changeset/event.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | class Changeset 4 | class Event 5 | attr_reader :name 6 | 7 | # - if at the time the event is added, we know all params 8 | # raw_payload would be a Hash 9 | # - else, it means we need to wait for db operations to be committed 10 | # in this case, raw_payload would be a Proc 11 | # example: we need db id of some model which is not created yet 12 | def initialize(name:, raw_payload:, events_catalog:) 13 | raise Changeset::Errors::UnknownEventError.new("unknown #{name}") unless events_catalog.known_event?(name) 14 | 15 | @name = name 16 | @events_catalog = events_catalog 17 | @raw_payload = raw_payload 18 | end 19 | 20 | def dispatch 21 | events_catalog.dispatch(self) 22 | end 23 | 24 | def unicity_key 25 | [events_catalog.class, name, payload] 26 | end 27 | 28 | def payload 29 | if raw_payload.is_a?(Proc) 30 | raw_payload.call 31 | else 32 | raw_payload 33 | end 34 | end 35 | 36 | def ==(other) 37 | unicity_key == other.unicity_key 38 | end 39 | 40 | private 41 | 42 | attr_reader :events_catalog, :raw_payload 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | ## Specific to RubyMotion: 20 | .dat* 21 | .repl_history 22 | build/ 23 | *.bridgesupport 24 | build-iPhoneOS/ 25 | build-iPhoneSimulator/ 26 | 27 | ## Specific to RubyMotion (use of CocoaPods): 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 32 | # 33 | # vendor/Pods/ 34 | 35 | ## Documentation cache and generated files: 36 | /.yardoc/ 37 | /_yardoc/ 38 | /rdoc/ 39 | 40 | ## Environment normalization: 41 | /.bundle/ 42 | /vendor/bundle 43 | /lib/bundler/man/ 44 | 45 | # for a library or gem, you might want to ignore these files since the code is 46 | # intended to run in multiple environments; otherwise, check them in: 47 | # Gemfile.lock 48 | # .ruby-version 49 | # .ruby-gemset 50 | 51 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 52 | .rvmrc 53 | 54 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 55 | # .rubocop-https?--* 56 | -------------------------------------------------------------------------------- /spec/changeset/changeset_integration_spec.rb: -------------------------------------------------------------------------------- 1 | # # typed: ignore 2 | 3 | RSpec.describe "Changeset Integration", with_sorbet: false do 4 | let(:mocked_worker) { spy("mocked_worker") } 5 | let(:event_catalog_klass) do 6 | Class.new do 7 | def initialize(mocked_worker) 8 | @mocked_worker = mocked_worker 9 | end 10 | 11 | def dispatch(event) 12 | send(event.name, event.payload) 13 | end 14 | 15 | def known_event?(event_name) 16 | %i[my_event].include?(event_name) 17 | end 18 | 19 | private 20 | 21 | def my_event(payload) 22 | @mocked_worker.call(payload) 23 | end 24 | end 25 | end 26 | let(:db_operation1) { spy("db_operation1") } 27 | let(:db_operation2) { spy("db_operation2") } 28 | let(:changeset) { ::Changeset.new(event_catalog_klass.new(mocked_worker)) } 29 | 30 | before do 31 | Changeset.configure do |config| 32 | config.db_transaction_wrapper = ->(&block) { block.call } 33 | end 34 | end 35 | 36 | it "triggers db_operations and unique events" do 37 | changeset.add_db_operations( 38 | db_operation1, 39 | db_operation2 40 | ) 41 | changeset.add_event(:my_event, {"foo" => 1}) 42 | changeset.add_event(:my_event, -> { {"foo" => 1} }) 43 | 44 | changeset.push! 45 | 46 | expect(db_operation1).to have_received(:call).ordered 47 | expect(db_operation2).to have_received(:call).ordered 48 | expect(mocked_worker).to have_received(:call).with({"foo" => 1}).once.ordered 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/changeset/merge_child_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: ignore 2 | 3 | RSpec.describe "Changeset Merge Child", with_sorbet: false do 4 | let(:mocked_worker) { spy("mocked_worker") } 5 | let(:mocked_worker2) { spy("mocked_worker2") } 6 | let(:event_catalog_klass) do 7 | Class.new do 8 | def initialize(mocked_worker) 9 | @mocked_worker = mocked_worker 10 | end 11 | 12 | def dispatch(event) 13 | send(event.name, event.payload) 14 | end 15 | 16 | def known_event?(event_name) 17 | %i[my_event].include?(event_name) 18 | end 19 | 20 | private 21 | 22 | def my_event(payload) 23 | @mocked_worker.call(payload) 24 | end 25 | end 26 | end 27 | 28 | let(:db_operation1) { spy("db_operation1") } 29 | let(:db_operation2) { spy("db_operation2") } 30 | let(:db_operation3) { spy("db_operation3") } 31 | let(:changeset) { ::Changeset.new(event_catalog_klass.new(mocked_worker)) } 32 | let(:child_changeset) { ::Changeset.new(event_catalog_klass.new(mocked_worker2)) } 33 | 34 | before do 35 | Changeset.configure do |config| 36 | config.db_transaction_wrapper = ->(&block) { block.call } 37 | end 38 | end 39 | 40 | it "triggers db_operations and unique events" do 41 | changeset.add_db_operations( 42 | db_operation1, 43 | db_operation2 44 | ) 45 | changeset.add_event(:my_event, {"foo" => 1}) 46 | 47 | child_changeset.add_db_operation(db_operation3) 48 | child_changeset.add_event(:my_event, {"foo" => 2}) 49 | 50 | changeset.merge_child(child_changeset) 51 | 52 | changeset.push! 53 | 54 | expect(db_operation1).to have_received(:call).ordered 55 | expect(db_operation2).to have_received(:call).ordered 56 | expect(db_operation3).to have_received(:call).ordered 57 | expect(mocked_worker).to have_received(:call).with({"foo" => 1}).once.ordered 58 | expect(mocked_worker2).to have_received(:call).with({"foo" => 2}).once.ordered 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/changeset/event_collection.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | class Changeset 4 | class EventCollection 5 | def initialize 6 | @grouped_events = {} 7 | @async_change_sets = [] 8 | end 9 | 10 | def add(name:, raw_payload:, events_catalog:) 11 | new_event = Event.new( 12 | name: name, 13 | raw_payload: raw_payload, 14 | events_catalog: events_catalog 15 | ) 16 | add_event(new_event) 17 | end 18 | 19 | def merge_child(event_collection) 20 | event_collection.all_events.each do |event| 21 | add_event(event) 22 | end 23 | event_collection.async_change_sets.each do |async_change_set| 24 | async_change_sets.push(async_change_set) 25 | end 26 | end 27 | 28 | def merge_child_async(async_change_set) 29 | async_change_sets.push(async_change_set) 30 | self 31 | end 32 | 33 | # standard:disable Style/ArgumentsForwarding 34 | def each(&block) 35 | uniq_events.each(&block) 36 | end 37 | # standard:enable Style/ArgumentsForwarding 38 | 39 | def ==(other) 40 | uniq_events == other.uniq_events 41 | end 42 | 43 | protected 44 | 45 | attr_reader :grouped_events, :async_change_sets 46 | 47 | # only used for merge 48 | def all_events 49 | [].tap do |collection| 50 | grouped_events.each_value do |events| 51 | collection.concat(events) 52 | end 53 | end 54 | end 55 | 56 | # called after push through #each 57 | def uniq_events 58 | async_change_sets.each do |async_change_set| 59 | async_change_set.events_collection.each do |event| 60 | add_event(event) 61 | end 62 | end 63 | 64 | [].tap do |collection| 65 | grouped_events.each_value do |events| 66 | collection.concat(events.uniq { |event| event.unicity_key }) 67 | end 68 | end 69 | end 70 | 71 | private 72 | 73 | def add_event(event) 74 | grouped_events[event.name] ||= [] 75 | grouped_events.fetch(event.name).push(event) 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/changeset.rb: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | require "zeitwerk" 4 | loader = Zeitwerk::Loader.for_gem 5 | loader.setup 6 | 7 | class Changeset 8 | def initialize(events_catalog = Changeset::NullEventCatalog.new) 9 | @events_collection = Changeset::EventCollection.new 10 | @db_operations = ::Changeset::DbOperationCollection.new 11 | @events_catalog = events_catalog 12 | end 13 | 14 | def merge_child(change_set) 15 | events_collection.merge_child(change_set.events_collection) 16 | db_operations.merge_child(change_set.db_operations) 17 | self 18 | end 19 | 20 | def merge_child_async(&changeset_wrapped_in_proc) 21 | async_change_set = ::Changeset::AsyncChangeset.new(changeset_wrapped_in_proc) 22 | events_collection.merge_child_async(async_change_set) 23 | db_operations.merge_child_async(async_change_set) 24 | self 25 | end 26 | 27 | def add_event(name, raw_payload) 28 | events_collection.add(name: name, raw_payload: raw_payload, events_catalog: events_catalog) 29 | self 30 | end 31 | 32 | def add_db_operations(*persistence_handlers) 33 | persistence_handlers.each do |persistence_handler| 34 | add_db_operation(persistence_handler) 35 | end 36 | self 37 | end 38 | 39 | def add_db_operation(persistence_handler) 40 | db_operations.add(persistence_handler) 41 | self 42 | end 43 | 44 | def push! 45 | commit_db_operations 46 | dispatch_events 47 | self 48 | end 49 | 50 | def commit_db_operations 51 | Changeset.configuration.db_transaction_wrapper.call do 52 | db_operations.each(&:call) 53 | end 54 | end 55 | 56 | def dispatch_events 57 | events_collection.each do |event| 58 | event.dispatch 59 | end 60 | end 61 | 62 | def ==(other) 63 | db_operations == other.db_operations && 64 | events_collection == other.events_collection 65 | end 66 | 67 | def self.configuration 68 | @configuration ||= Changeset::Configuration.new 69 | end 70 | 71 | def self.configure(&block) 72 | block.call(configuration) 73 | end 74 | 75 | protected 76 | 77 | attr_reader :events_collection, :db_operations, :events_catalog 78 | end 79 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec@3.11.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `rspec` gem. 5 | # Please instead update this file by running `bin/tapioca gem rspec`. 6 | 7 | # source://rspec//lib/rspec/version.rb#1 8 | module RSpec 9 | class << self 10 | # source://rspec-core/3.11.0/lib/rspec/core.rb#70 11 | def clear_examples; end 12 | 13 | # source://rspec-core/3.11.0/lib/rspec/core.rb#85 14 | def configuration; end 15 | 16 | # source://rspec-core/3.11.0/lib/rspec/core.rb#49 17 | def configuration=(_arg0); end 18 | 19 | # source://rspec-core/3.11.0/lib/rspec/core.rb#97 20 | def configure; end 21 | 22 | # source://rspec-core/3.11.0/lib/rspec/core.rb#194 23 | def const_missing(name); end 24 | 25 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 26 | def context(*args, &example_group_block); end 27 | 28 | # source://rspec-core/3.11.0/lib/rspec/core.rb#122 29 | def current_example; end 30 | 31 | # source://rspec-core/3.11.0/lib/rspec/core.rb#128 32 | def current_example=(example); end 33 | 34 | # source://rspec-core/3.11.0/lib/rspec/core.rb#154 35 | def current_scope; end 36 | 37 | # source://rspec-core/3.11.0/lib/rspec/core.rb#134 38 | def current_scope=(scope); end 39 | 40 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 41 | def describe(*args, &example_group_block); end 42 | 43 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 44 | def example_group(*args, &example_group_block); end 45 | 46 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 47 | def fcontext(*args, &example_group_block); end 48 | 49 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 50 | def fdescribe(*args, &example_group_block); end 51 | 52 | # source://rspec-core/3.11.0/lib/rspec/core.rb#58 53 | def reset; end 54 | 55 | # source://rspec-core/3.11.0/lib/rspec/core/shared_example_group.rb#110 56 | def shared_context(name, *args, &block); end 57 | 58 | # source://rspec-core/3.11.0/lib/rspec/core/shared_example_group.rb#110 59 | def shared_examples(name, *args, &block); end 60 | 61 | # source://rspec-core/3.11.0/lib/rspec/core/shared_example_group.rb#110 62 | def shared_examples_for(name, *args, &block); end 63 | 64 | # source://rspec-core/3.11.0/lib/rspec/core.rb#160 65 | def world; end 66 | 67 | # source://rspec-core/3.11.0/lib/rspec/core.rb#49 68 | def world=(_arg0); end 69 | 70 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 71 | def xcontext(*args, &example_group_block); end 72 | 73 | # source://rspec-core/3.11.0/lib/rspec/core/dsl.rb#42 74 | def xdescribe(*args, &example_group_block); end 75 | end 76 | end 77 | 78 | # source://rspec-core/3.11.0/lib/rspec/core.rb#187 79 | RSpec::MODULES_TO_AUTOLOAD = T.let(T.unsafe(nil), Hash) 80 | 81 | # source://rspec-core/3.11.0/lib/rspec/core/shared_context.rb#54 82 | RSpec::SharedContext = RSpec::Core::SharedContext 83 | 84 | # source://rspec//lib/rspec/version.rb#2 85 | module RSpec::Version; end 86 | 87 | # source://rspec//lib/rspec/version.rb#3 88 | RSpec::Version::STRING = T.let(T.unsafe(nil), String) 89 | -------------------------------------------------------------------------------- /doc/combo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | changeset (0.1.4) 5 | zeitwerk 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ast (2.4.2) 11 | byebug (11.1.3) 12 | coderay (1.1.3) 13 | diff-lcs (1.5.0) 14 | json (2.7.2) 15 | language_server-protocol (3.17.0.3) 16 | lint_roller (1.1.0) 17 | method_source (1.0.0) 18 | parallel (1.25.1) 19 | parser (3.3.4.0) 20 | ast (~> 2.4.1) 21 | racc 22 | pry (0.14.1) 23 | coderay (~> 1.1) 24 | method_source (~> 1.0) 25 | racc (1.8.1) 26 | rainbow (3.1.1) 27 | rbi (0.0.17) 28 | ast 29 | parser (>= 3.0.0) 30 | sorbet-runtime (>= 0.5.9204) 31 | unparser (>= 0.5.6) 32 | regexp_parser (2.9.2) 33 | rexml (3.3.4) 34 | strscan 35 | rspec (3.11.0) 36 | rspec-core (~> 3.11.0) 37 | rspec-expectations (~> 3.11.0) 38 | rspec-mocks (~> 3.11.0) 39 | rspec-core (3.11.0) 40 | rspec-support (~> 3.11.0) 41 | rspec-expectations (3.11.1) 42 | diff-lcs (>= 1.2.0, < 2.0) 43 | rspec-support (~> 3.11.0) 44 | rspec-mocks (3.11.1) 45 | diff-lcs (>= 1.2.0, < 2.0) 46 | rspec-support (~> 3.11.0) 47 | rspec-support (3.11.1) 48 | rubocop (1.64.1) 49 | json (~> 2.3) 50 | language_server-protocol (>= 3.17.0) 51 | parallel (~> 1.10) 52 | parser (>= 3.3.0.2) 53 | rainbow (>= 2.2.2, < 4.0) 54 | regexp_parser (>= 1.8, < 3.0) 55 | rexml (>= 3.2.5, < 4.0) 56 | rubocop-ast (>= 1.31.1, < 2.0) 57 | ruby-progressbar (~> 1.7) 58 | unicode-display_width (>= 2.4.0, < 3.0) 59 | rubocop-ast (1.31.3) 60 | parser (>= 3.3.1.0) 61 | rubocop-performance (1.21.1) 62 | rubocop (>= 1.48.1, < 2.0) 63 | rubocop-ast (>= 1.31.1, < 2.0) 64 | ruby-progressbar (1.13.0) 65 | sorbet (0.5.9204) 66 | sorbet-static (= 0.5.9204) 67 | sorbet-runtime (0.5.9204) 68 | sorbet-static (0.5.9204-universal-darwin-19) 69 | sorbet-static (0.5.9204-universal-darwin-20) 70 | sorbet-static (0.5.9204-universal-darwin-21) 71 | sorbet-static (0.5.9204-x86_64-linux) 72 | spoom (1.1.13) 73 | sorbet (>= 0.5.9204) 74 | sorbet-runtime (>= 0.5.9204) 75 | thor (>= 0.19.2) 76 | standard (1.39.2) 77 | language_server-protocol (~> 3.17.0.2) 78 | lint_roller (~> 1.0) 79 | rubocop (~> 1.64.0) 80 | standard-custom (~> 1.0.0) 81 | standard-performance (~> 1.4) 82 | standard-custom (1.0.2) 83 | lint_roller (~> 1.0) 84 | rubocop (~> 1.50) 85 | standard-performance (1.4.0) 86 | lint_roller (~> 1.1) 87 | rubocop-performance (~> 1.21.0) 88 | strscan (3.1.0) 89 | tapioca (0.7.3) 90 | bundler (>= 1.17.3) 91 | pry (>= 0.12.2) 92 | rbi (~> 0.0.0, >= 0.0.14) 93 | sorbet-runtime (>= 0.5.9204) 94 | sorbet-static (>= 0.5.9204) 95 | spoom (~> 1.1.0, >= 1.1.11) 96 | thor (>= 1.2.0) 97 | yard-sorbet 98 | thor (1.2.1) 99 | unicode-display_width (2.5.0) 100 | unparser (0.6.5) 101 | diff-lcs (~> 1.3) 102 | parser (>= 3.1.0) 103 | webrick (1.7.0) 104 | yard (0.9.28) 105 | webrick (~> 1.7.0) 106 | yard-sorbet (0.7.0) 107 | sorbet-runtime (>= 0.5) 108 | yard (>= 0.9) 109 | zeitwerk (2.6.6) 110 | 111 | PLATFORMS 112 | arm64-darwin-21 113 | x86_64-darwin-19 114 | x86_64-darwin-20 115 | x86_64-linux 116 | 117 | DEPENDENCIES 118 | byebug 119 | changeset! 120 | rspec (= 3.11.0) 121 | sorbet (= 0.5.9204) 122 | sorbet-runtime (= 0.5.9204) 123 | standard 124 | tapioca 125 | 126 | BUNDLED WITH 127 | 2.5.9 128 | -------------------------------------------------------------------------------- /spec/changeset/changeset_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: ignore 2 | 3 | RSpec.describe Changeset, with_sorbet: false do 4 | let(:event_catalog_klass) do 5 | Class.new do 6 | def dispatch(event) 7 | send(event.name, event) 8 | end 9 | 10 | def known_event?(event_name) 11 | %i[my_event].include?(event_name) 12 | end 13 | 14 | private 15 | 16 | def my_event(event) 17 | end 18 | end 19 | end 20 | 21 | let(:other_event_catalog_klass) do 22 | Class.new do 23 | def dispatch(event) 24 | send(event.name, event) 25 | end 26 | 27 | def known_event?(event_name) 28 | %i[my_event].include?(event_name) 29 | end 30 | 31 | private 32 | 33 | def my_event(event) 34 | end 35 | end 36 | end 37 | 38 | let(:db_operation_klass) do 39 | Class.new do 40 | def initialize(some_param) 41 | @some_param = some_param 42 | end 43 | 44 | def ==(other) 45 | some_param == other.some_param 46 | end 47 | 48 | protected 49 | 50 | attr_reader :some_param 51 | end 52 | end 53 | 54 | before do 55 | Changeset.configure do |config| 56 | config.db_transaction_wrapper = ->(&block) { block.call } 57 | end 58 | end 59 | 60 | context "comparing changesets" do 61 | let(:changeset1) { Changeset.new(event_catalog_klass.new) } 62 | let(:changeset2) { Changeset.new(event_catalog_klass.new) } 63 | 64 | context "when empty" do 65 | it "is equal" do 66 | expect(changeset1).to eq(changeset2) 67 | end 68 | end 69 | 70 | context "only db operations" do 71 | it "returns true when same collections" do 72 | changeset1.add_db_operations( 73 | db_operation_klass.new("db_operation1"), 74 | db_operation_klass.new("db_operation2") 75 | ) 76 | 77 | changeset2.add_db_operations( 78 | db_operation_klass.new("db_operation1"), 79 | db_operation_klass.new("db_operation2") 80 | ) 81 | 82 | expect(changeset1).to eq(changeset2) 83 | end 84 | 85 | it "returns false when different collections" do 86 | changeset1.add_db_operations( 87 | db_operation_klass.new("db_operation1"), 88 | db_operation_klass.new("db_operation2") 89 | ) 90 | 91 | changeset2.add_db_operations( 92 | db_operation_klass.new("db_operation2"), 93 | db_operation_klass.new("db_operation1") 94 | ) 95 | 96 | expect(changeset1).to_not eql(changeset2) 97 | end 98 | end 99 | 100 | context "only events" do 101 | it "returns true when uniq events with same payload" do 102 | changeset1.add_event(:my_event, {"foo" => 1}) 103 | changeset1.add_event(:my_event, {"foo" => 1}) 104 | 105 | changeset2.add_event(:my_event, {"foo" => 1}) 106 | 107 | expect(changeset1).to eq(changeset2) 108 | end 109 | 110 | # not sure we should do this 111 | it "returns true when uniq events with same payload (evaluates blocks)" do 112 | changeset1.add_event(:my_event, {"foo" => 1}) 113 | 114 | changeset2.add_event(:my_event, -> { {"foo" => 1} }) 115 | 116 | expect(changeset1).to eq(changeset2) 117 | end 118 | 119 | it "returns false when events with different payloads" do 120 | changeset1.add_event(:my_event, {"foo" => 1}) 121 | 122 | changeset2.add_event(:my_event, {"foo" => 2}) 123 | 124 | expect(changeset1).to_not eq(changeset2) 125 | end 126 | 127 | it "returns false when events with same payload (evaluates blocks)" do 128 | changeset1.add_event(:my_event, -> { {"foo" => 1} }) 129 | 130 | changeset2.add_event(:my_event, -> { {"foo" => 2} }) 131 | 132 | expect(changeset1).to_not eq(changeset2) 133 | end 134 | 135 | context "different events catalogs" do 136 | let(:changeset2) { Changeset.new(other_event_catalog_klass.new) } 137 | 138 | it "returns false event same payload and name" do 139 | changeset1.add_event(:my_event, {"foo" => 1}) 140 | 141 | changeset2.add_event(:my_event, {"foo" => 1}) 142 | 143 | expect(changeset1).to_not eq(changeset2) 144 | end 145 | end 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/netrc@0.11.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `netrc` gem. 5 | # Please instead update this file by running `bin/tapioca gem netrc`. 6 | 7 | # source://netrc//lib/netrc.rb#3 8 | class Netrc 9 | # @return [Netrc] a new instance of Netrc 10 | # 11 | # source://netrc//lib/netrc.rb#166 12 | def initialize(path, data); end 13 | 14 | # source://netrc//lib/netrc.rb#180 15 | def [](k); end 16 | 17 | # source://netrc//lib/netrc.rb#188 18 | def []=(k, info); end 19 | 20 | # source://netrc//lib/netrc.rb#200 21 | def delete(key); end 22 | 23 | # source://netrc//lib/netrc.rb#211 24 | def each(&block); end 25 | 26 | # source://netrc//lib/netrc.rb#196 27 | def length; end 28 | 29 | # source://netrc//lib/netrc.rb#215 30 | def new_item(m, l, p); end 31 | 32 | # Returns the value of attribute new_item_prefix. 33 | # 34 | # source://netrc//lib/netrc.rb#178 35 | def new_item_prefix; end 36 | 37 | # Sets the attribute new_item_prefix 38 | # 39 | # @param value the value to set the attribute new_item_prefix to. 40 | # 41 | # source://netrc//lib/netrc.rb#178 42 | def new_item_prefix=(_arg0); end 43 | 44 | # source://netrc//lib/netrc.rb#219 45 | def save; end 46 | 47 | # source://netrc//lib/netrc.rb#233 48 | def unparse; end 49 | 50 | class << self 51 | # source://netrc//lib/netrc.rb#42 52 | def check_permissions(path); end 53 | 54 | # source://netrc//lib/netrc.rb#33 55 | def config; end 56 | 57 | # @yield [self.config] 58 | # 59 | # source://netrc//lib/netrc.rb#37 60 | def configure; end 61 | 62 | # source://netrc//lib/netrc.rb#10 63 | def default_path; end 64 | 65 | # source://netrc//lib/netrc.rb#14 66 | def home_path; end 67 | 68 | # source://netrc//lib/netrc.rb#85 69 | def lex(lines); end 70 | 71 | # source://netrc//lib/netrc.rb#29 72 | def netrc_filename; end 73 | 74 | # Returns two values, a header and a list of items. 75 | # Each item is a tuple, containing some or all of: 76 | # - machine keyword (including trailing whitespace+comments) 77 | # - machine name 78 | # - login keyword (including surrounding whitespace+comments) 79 | # - login 80 | # - password keyword (including surrounding whitespace+comments) 81 | # - password 82 | # - trailing chars 83 | # This lets us change individual fields, then write out the file 84 | # with all its original formatting. 85 | # 86 | # source://netrc//lib/netrc.rb#129 87 | def parse(ts); end 88 | 89 | # Reads path and parses it as a .netrc file. If path doesn't 90 | # exist, returns an empty object. Decrypt paths ending in .gpg. 91 | # 92 | # source://netrc//lib/netrc.rb#51 93 | def read(path = T.unsafe(nil)); end 94 | 95 | # @return [Boolean] 96 | # 97 | # source://netrc//lib/netrc.rb#112 98 | def skip?(s); end 99 | end 100 | end 101 | 102 | # source://netrc//lib/netrc.rb#244 103 | class Netrc::Entry < ::Struct 104 | # Returns the value of attribute login 105 | # 106 | # @return [Object] the current value of login 107 | def login; end 108 | 109 | # Sets the attribute login 110 | # 111 | # @param value [Object] the value to set the attribute login to. 112 | # @return [Object] the newly set value 113 | def login=(_); end 114 | 115 | # Returns the value of attribute password 116 | # 117 | # @return [Object] the current value of password 118 | def password; end 119 | 120 | # Sets the attribute password 121 | # 122 | # @param value [Object] the value to set the attribute password to. 123 | # @return [Object] the newly set value 124 | def password=(_); end 125 | 126 | def to_ary; end 127 | 128 | class << self 129 | def [](*_arg0); end 130 | def inspect; end 131 | def keyword_init?; end 132 | def members; end 133 | def new(*_arg0); end 134 | end 135 | end 136 | 137 | # source://netrc//lib/netrc.rb#250 138 | class Netrc::Error < ::StandardError; end 139 | 140 | # source://netrc//lib/netrc.rb#68 141 | class Netrc::TokenArray < ::Array 142 | # source://netrc//lib/netrc.rb#76 143 | def readto; end 144 | 145 | # source://netrc//lib/netrc.rb#69 146 | def take; end 147 | end 148 | 149 | # source://netrc//lib/netrc.rb#4 150 | Netrc::VERSION = T.let(T.unsafe(nil), String) 151 | -------------------------------------------------------------------------------- /spec/changeset/merge_child_async_spec.rb: -------------------------------------------------------------------------------- 1 | # typed: ignore 2 | 3 | RSpec.describe "Changeset Merge Child Async", with_sorbet: false do 4 | context "check calls" do 5 | let(:mocked_worker) { spy("mocked_worker") } 6 | let(:mocked_worker2) { spy("mocked_worker2") } 7 | let(:mocked_worker3) { spy("mocked_worker3") } 8 | let(:event_catalog_klass) do 9 | Class.new do 10 | def initialize(mocked_worker) 11 | @mocked_worker = mocked_worker 12 | end 13 | 14 | def dispatch(event) 15 | send(event.name, event.payload) 16 | end 17 | 18 | def known_event?(event_name) 19 | %i[my_event].include?(event_name) 20 | end 21 | 22 | private 23 | 24 | def my_event(payload) 25 | @mocked_worker.call(payload) 26 | end 27 | end 28 | end 29 | 30 | let(:db_operation1) { spy("db_operation1") } 31 | let(:db_operation2) { spy("db_operation2") } 32 | let(:db_operation3) { spy("db_operation3") } 33 | let(:db_operation4) { spy("db_operation4") } 34 | let(:changeset) { ::Changeset.new(event_catalog_klass.new(mocked_worker)) } 35 | let(:child_changeset) { ::Changeset.new(event_catalog_klass.new(mocked_worker2)) } 36 | let(:grand_child_changeset) { ::Changeset.new(event_catalog_klass.new(mocked_worker3)) } 37 | 38 | before do 39 | Changeset.configure do |config| 40 | config.db_transaction_wrapper = ->(&block) { block.call } 41 | end 42 | end 43 | 44 | it "triggers db_operations and events" do 45 | changeset.add_db_operations( 46 | db_operation1, 47 | db_operation2 48 | ) 49 | changeset.add_event(:my_event, {"foo" => 1}) 50 | 51 | changeset.merge_child_async do 52 | child_changeset 53 | .add_db_operation(db_operation3) 54 | .add_event(:my_event, {"foo" => 2}) 55 | .merge_child_async do 56 | grand_child_changeset 57 | .add_db_operations(db_operation4) 58 | .add_event(:my_event, {"foo" => 3}) 59 | end 60 | end 61 | 62 | changeset.push! 63 | 64 | expect(db_operation1).to have_received(:call).ordered 65 | expect(db_operation2).to have_received(:call).ordered 66 | expect(db_operation3).to have_received(:call).ordered 67 | expect(db_operation4).to have_received(:call).ordered 68 | expect(mocked_worker).to have_received(:call).with({"foo" => 1}).once.ordered 69 | expect(mocked_worker2).to have_received(:call).with({"foo" => 2}).once.ordered 70 | expect(mocked_worker3).to have_received(:call).with({"foo" => 3}).once.ordered 71 | end 72 | end 73 | 74 | context "check instanciation order" do 75 | it "triggers db_operations" do 76 | child_changeset_instantiated = false 77 | grand_child_changeset_instantiated = false 78 | 79 | db_operation1_called = false 80 | db_operation2_called = false 81 | db_operation3_called = false 82 | db_operation4_called = false 83 | db_operation5_called = false 84 | 85 | db_operation1 = -> { 86 | db_operation1_called = true 87 | expect(child_changeset_instantiated).to be false 88 | } 89 | 90 | db_operation2 = -> { 91 | db_operation2_called = true 92 | expect(grand_child_changeset_instantiated).to be false 93 | } 94 | 95 | db_operation3 = -> { 96 | db_operation3_called = true 97 | } 98 | 99 | db_operation4 = -> { 100 | db_operation4_called = true 101 | expect(grand_child_changeset_instantiated).to be true 102 | } 103 | 104 | db_operation5 = -> { 105 | db_operation5_called = true 106 | expect(child_changeset_instantiated).to be true 107 | expect(grand_child_changeset_instantiated).to be true 108 | } 109 | 110 | Changeset.new.add_db_operations( 111 | db_operation1 112 | ).merge_child_async do 113 | child_changeset_instantiated = true 114 | Changeset.new 115 | .add_db_operation(db_operation2) 116 | .merge_child_async do 117 | grand_child_changeset_instantiated = true 118 | Changeset.new 119 | .add_db_operations(db_operation3) 120 | end 121 | .add_db_operation(db_operation4) 122 | end 123 | .add_db_operation(db_operation5) 124 | .push! 125 | 126 | expect(db_operation1_called).to be true 127 | expect(db_operation2_called).to be true 128 | expect(db_operation3_called).to be true 129 | expect(db_operation4_called).to be true 130 | expect(db_operation5_called).to be true 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | 3 | require "rubygems" 4 | require "sorbet-runtime" 5 | require "byebug" 6 | require_relative "../lib/changeset" 7 | 8 | # This file was generated by the `rspec --init` command. Conventionally, all 9 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 10 | # The generated `.rspec` file contains `--require spec_helper` which will cause 11 | # this file to always be loaded, without a need to explicitly require it in any 12 | # files. 13 | # 14 | # Given that it is always loaded, you are encouraged to keep this file as 15 | # light-weight as possible. Requiring heavyweight dependencies from this file 16 | # will add to the boot time of your test suite on EVERY test run, even for an 17 | # individual file that may not need all of that loaded. Instead, consider making 18 | # a separate helper file that requires the additional dependencies and performs 19 | # the additional setup, and require it from the spec files that actually need 20 | # it. 21 | # 22 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 23 | RSpec.configure do |config| 24 | # rspec-expectations config goes here. You can use an alternate 25 | # assertion/expectation library such as wrong or the stdlib/minitest 26 | # assertions if you prefer. 27 | config.expect_with :rspec do |expectations| 28 | # This option will default to `true` in RSpec 4. It makes the `description` 29 | # and `failure_message` of custom matchers include text for helper methods 30 | # defined using `chain`, e.g.: 31 | # be_bigger_than(2).and_smaller_than(4).description 32 | # # => "be bigger than 2 and smaller than 4" 33 | # ...rather than: 34 | # # => "be bigger than 2" 35 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 36 | end 37 | 38 | # rspec-mocks config goes here. You can use an alternate test double 39 | # library (such as bogus or mocha) by changing the `mock_with` option here. 40 | config.mock_with :rspec do |mocks| 41 | # Prevents you from mocking or stubbing a method that does not exist on 42 | # a real object. This is generally recommended, and will default to 43 | # `true` in RSpec 4. 44 | mocks.verify_partial_doubles = true 45 | end 46 | 47 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 48 | # have no way to turn it off -- the option exists only for backwards 49 | # compatibility in RSpec 3). It causes shared context metadata to be 50 | # inherited by the metadata hash of host groups and examples, rather than 51 | # triggering implicit auto-inclusion in groups with matching metadata. 52 | config.shared_context_metadata_behavior = :apply_to_host_groups 53 | 54 | # The settings below are suggested to provide a good initial experience 55 | # with RSpec, but feel free to customize to your heart's content. 56 | # # This allows you to limit a spec run to individual examples or groups 57 | # # you care about by tagging them with `:focus` metadata. When nothing 58 | # # is tagged with `:focus`, all examples get run. RSpec also provides 59 | # # aliases for `it`, `describe`, and `context` that include `:focus` 60 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 61 | # config.filter_run_when_matching :focus 62 | # 63 | # # Allows RSpec to persist some state between runs in order to support 64 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 65 | # # you configure your source control system to ignore this file. 66 | # config.example_status_persistence_file_path = "spec/examples.txt" 67 | # 68 | # # Limits the available syntax to the non-monkey patched syntax that is 69 | # # recommended. For more details, see: 70 | # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 71 | # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 72 | # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 73 | # config.disable_monkey_patching! 74 | # 75 | # # This setting enables warnings. It's recommended, but in some cases may 76 | # # be too noisy due to issues in dependencies. 77 | # config.warnings = true 78 | # 79 | # # Many RSpec users commonly either run the entire suite or an individual 80 | # # file, and it's useful to allow more verbose output when running an 81 | # # individual spec file. 82 | # if config.files_to_run.one? 83 | # # Use the documentation formatter for detailed output, 84 | # # unless a formatter has already been configured 85 | # # (e.g. via a command-line flag). 86 | # config.default_formatter = "doc" 87 | # end 88 | # 89 | # # Print the 10 slowest examples and example groups at the 90 | # # end of the spec run, to help surface which specs are running 91 | # # particularly slow. 92 | # config.profile_examples = 10 93 | # 94 | # # Run specs in random order to surface order dependencies. If you find an 95 | # # order dependency and want to debug it, you can fix the order by providing 96 | # # the seed, which is printed after each run. 97 | # # --seed 1234 98 | # config.order = :random 99 | # 100 | # # Seed global randomization in this process using the `--seed` CLI option. 101 | # # Setting this allows you to use `--seed` to deterministically reproduce 102 | # # test failures related to randomization by passing the same `--seed` value 103 | # # as the one that triggered the failure. 104 | # Kernel.srand config.seed 105 | 106 | config.around(:each) do |example| 107 | if example.metadata[:with_sorbet] 108 | example.run 109 | else 110 | T::Configuration.call_validation_error_handler = lambda do |signature, opts| 111 | puts opts[:pretty_message] 112 | end 113 | 114 | T::Configuration.inline_type_error_handler = lambda do |error, opts| 115 | puts error.message 116 | end 117 | 118 | example.run 119 | 120 | # if custom value is nil, it goes to the default sorbet behaviour 121 | T::Configuration.inline_type_error_handler = nil 122 | T::Configuration.call_validation_error_handler = nil 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /rbi/changeset.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | class Changeset 4 | EventPayload = T.type_alias { T.untyped } 5 | Callable = T.type_alias { T.any(Changeset::PersistenceInterface, T.proc.void) } 6 | RawEventPayload = T.type_alias { T.any(EventPayload, T.proc.returns(EventPayload)) } 7 | 8 | sig { params(events_catalog: ::Changeset::EventCatalogInterface).void } 9 | def initialize(events_catalog = Changeset::NullEventCatalog.new) 10 | end 11 | 12 | sig { params(change_set: Changeset).returns(T.self_type) } 13 | def merge_child(change_set) 14 | end 15 | 16 | sig { params(changeset_wrapped_in_proc: T.proc.returns(::Changeset)).returns(T.self_type) } 17 | def merge_child_async(&changeset_wrapped_in_proc) 18 | end 19 | 20 | sig { params(name: Symbol, raw_payload: Changeset::RawEventPayload).returns(T.self_type) } 21 | def add_event(name, raw_payload) 22 | end 23 | 24 | sig { params(persistence_handlers: Changeset::Callable).returns(T.self_type) } 25 | def add_db_operations(*persistence_handlers) 26 | end 27 | 28 | sig { params(persistence_handler: Changeset::Callable).returns(T.self_type) } 29 | def add_db_operation(persistence_handler) 30 | end 31 | 32 | sig { returns(T.self_type) } 33 | def push! 34 | end 35 | 36 | sig { void } 37 | def commit_db_operations 38 | end 39 | 40 | sig { void } 41 | def dispatch_events 42 | end 43 | 44 | sig { params(other: Changeset).returns(T::Boolean) } 45 | def ==(other) 46 | end 47 | 48 | sig { returns(Changeset::Configuration) } 49 | def self.configuration 50 | end 51 | 52 | sig { params(block: T.proc.params(block: Changeset::Configuration).void).void } 53 | def self.configure(&block) 54 | end 55 | 56 | class Configuration 57 | DbTransactionWrapper = T.type_alias { T.proc.params(block: T.proc.void).void } 58 | 59 | sig { params(db_transaction_wrapper: DbTransactionWrapper).returns(DbTransactionWrapper) } 60 | attr_writer :db_transaction_wrapper 61 | 62 | sig { returns(T.proc.void) } 63 | def db_transaction_wrapper 64 | end 65 | end 66 | 67 | module EventCatalogInterface 68 | extend T::Helpers 69 | abstract! 70 | 71 | sig { abstract.params(event: Changeset::Event).void } 72 | def dispatch(event) 73 | end 74 | 75 | sig { abstract.params(event_name: Symbol).returns(T::Boolean) } 76 | def known_event?(event_name) 77 | end 78 | 79 | sig { returns(Class) } 80 | def class 81 | end 82 | end 83 | 84 | class AsyncChangeset 85 | sig { params(changeset_wrapped_in_proc: T.proc.returns(::Changeset)).void } 86 | def initialize(changeset_wrapped_in_proc) 87 | end 88 | 89 | sig { returns(DbOperationCollection) } 90 | def db_operations 91 | end 92 | 93 | sig { returns(EventCollection) } 94 | def events_collection 95 | end 96 | end 97 | 98 | class DbOperationCollection 99 | CollectionElement = T.type_alias { T.any(Changeset::PersistenceInterface, T.proc.void, Changeset::AsyncChangeset) } 100 | 101 | sig { void } 102 | def initialize 103 | end 104 | 105 | sig { params(persistence_handler: CollectionElement).void } 106 | def add(persistence_handler) 107 | end 108 | 109 | sig { params(db_operations: Changeset::DbOperationCollection).void } 110 | def merge_child(db_operations) 111 | end 112 | 113 | sig { params(async_change_set: Changeset::AsyncChangeset).void } 114 | def merge_child_async(async_change_set) 115 | end 116 | 117 | sig { params(block: T.proc.params(arg0: Changeset::Callable).returns(BasicObject)).void } 118 | def each(&block) 119 | end 120 | 121 | sig { params(other: Changeset::DbOperationCollection).returns(T::Boolean) } 122 | def ==(other) 123 | end 124 | 125 | protected 126 | 127 | sig { returns(T::Array[CollectionElement]) } 128 | attr_reader :collection 129 | end 130 | 131 | class EventCollection 132 | GroupedEvent = T.type_alias { T::Hash[Symbol, T::Array[Changeset::Event]] } 133 | 134 | sig { void } 135 | def initialize 136 | end 137 | 138 | sig { params(name: Symbol, raw_payload: Changeset::RawEventPayload, events_catalog: ::Changeset::EventCatalogInterface).void } 139 | def add(name:, raw_payload:, events_catalog:) 140 | end 141 | 142 | sig { params(event_collection: Changeset::EventCollection).void } 143 | def merge_child(event_collection) 144 | end 145 | 146 | sig { params(async_change_set: Changeset::AsyncChangeset).void } 147 | def merge_child_async(async_change_set) 148 | end 149 | 150 | sig { params(block: T.proc.params(arg0: Changeset::Event).returns(BasicObject)).void } 151 | def each(&block) 152 | end 153 | 154 | sig { params(other: Changeset::EventCollection).returns(T::Boolean) } 155 | def ==(other) 156 | end 157 | 158 | protected 159 | 160 | sig { returns(GroupedEvent) } 161 | attr_reader :grouped_events 162 | sig { returns(T::Array[::Changeset::AsyncChangeset]) } 163 | attr_reader :async_change_sets 164 | 165 | # only used for merge 166 | sig { returns(T::Array[Changeset::Event]) } 167 | def all_events 168 | end 169 | 170 | sig { returns(T::Array[Changeset::Event]) } 171 | def uniq_events 172 | end 173 | 174 | private 175 | 176 | sig { params(event: Changeset::Event).void } 177 | def add_event(event) 178 | end 179 | end 180 | 181 | class Event 182 | sig { returns(Symbol) } 183 | attr_reader :name 184 | 185 | sig { params(name: Symbol, raw_payload: Changeset::RawEventPayload, events_catalog: ::Changeset::EventCatalogInterface).void } 186 | def initialize(name:, raw_payload:, events_catalog:) 187 | end 188 | 189 | sig { void } 190 | def dispatch 191 | end 192 | 193 | sig { returns(T::Array[T.untyped]) } 194 | def unicity_key 195 | end 196 | 197 | sig { returns(Changeset::EventPayload) } 198 | def payload 199 | end 200 | 201 | sig { params(other: Changeset::Event).returns(T::Boolean) } 202 | def ==(other) 203 | end 204 | 205 | private 206 | 207 | sig { returns(Changeset::EventCatalogInterface) } 208 | attr_reader :events_catalog 209 | 210 | sig { returns(Changeset::RawEventPayload) } 211 | attr_reader :raw_payload 212 | end 213 | 214 | class NullEventCatalog 215 | include Changeset::EventCatalogInterface 216 | 217 | sig { override.params(event: Changeset::Event).void } 218 | def dispatch(event) 219 | end 220 | 221 | sig { override.params(event_name: Symbol).returns(T::Boolean) } 222 | def known_event?(event_name) 223 | end 224 | end 225 | 226 | module PersistenceInterface 227 | extend T::Helpers 228 | abstract! 229 | 230 | sig { abstract.void } 231 | def call 232 | end 233 | end 234 | 235 | protected 236 | sig { returns(Changeset::DbOperationCollection) } 237 | attr_reader :db_operations 238 | sig { returns(Changeset::EventCollection) } 239 | attr_reader :events_collection 240 | sig { returns(::Changeset::EventCatalogInterface) } 241 | attr_reader :events_catalog 242 | end -------------------------------------------------------------------------------- /sorbet/rbi/gems/parallel@1.22.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `parallel` gem. 5 | # Please instead update this file by running `bin/tapioca gem parallel`. 6 | 7 | # source://parallel//lib/parallel/version.rb#2 8 | module Parallel 9 | extend ::Parallel::ProcessorCount 10 | 11 | class << self 12 | # @return [Boolean] 13 | # 14 | # source://parallel//lib/parallel.rb#246 15 | def all?(*args, &block); end 16 | 17 | # @return [Boolean] 18 | # 19 | # source://parallel//lib/parallel.rb#241 20 | def any?(*args, &block); end 21 | 22 | # source://parallel//lib/parallel.rb#237 23 | def each(array, options = T.unsafe(nil), &block); end 24 | 25 | # source://parallel//lib/parallel.rb#251 26 | def each_with_index(array, options = T.unsafe(nil), &block); end 27 | 28 | # source://parallel//lib/parallel.rb#306 29 | def flat_map(*args, &block); end 30 | 31 | # source://parallel//lib/parallel.rb#231 32 | def in_processes(options = T.unsafe(nil), &block); end 33 | 34 | # source://parallel//lib/parallel.rb#215 35 | def in_threads(options = T.unsafe(nil)); end 36 | 37 | # source://parallel//lib/parallel.rb#255 38 | def map(source, options = T.unsafe(nil), &block); end 39 | 40 | # source://parallel//lib/parallel.rb#302 41 | def map_with_index(array, options = T.unsafe(nil), &block); end 42 | 43 | # source://parallel//lib/parallel.rb#310 44 | def worker_number; end 45 | 46 | # TODO: this does not work when doing threads in forks, so should remove and yield the number instead if needed 47 | # 48 | # source://parallel//lib/parallel.rb#315 49 | def worker_number=(worker_num); end 50 | 51 | private 52 | 53 | # source://parallel//lib/parallel.rb#321 54 | def add_progress_bar!(job_factory, options); end 55 | 56 | # source://parallel//lib/parallel.rb#584 57 | def call_with_index(item, index, options, &block); end 58 | 59 | # source://parallel//lib/parallel.rb#516 60 | def create_workers(job_factory, options, &block); end 61 | 62 | # options is either a Integer or a Hash with :count 63 | # 64 | # source://parallel//lib/parallel.rb#574 65 | def extract_count_from_options(options); end 66 | 67 | # source://parallel//lib/parallel.rb#602 68 | def instrument_finish(item, index, result, options); end 69 | 70 | # source://parallel//lib/parallel.rb#607 71 | def instrument_start(item, index, options); end 72 | 73 | # source://parallel//lib/parallel.rb#550 74 | def process_incoming_jobs(read, write, job_factory, options, &block); end 75 | 76 | # source://parallel//lib/parallel.rb#504 77 | def replace_worker(job_factory, workers, index, options, blk); end 78 | 79 | # source://parallel//lib/parallel.rb#595 80 | def with_instrumentation(item, index, options); end 81 | 82 | # source://parallel//lib/parallel.rb#346 83 | def work_direct(job_factory, options, &block); end 84 | 85 | # source://parallel//lib/parallel.rb#456 86 | def work_in_processes(job_factory, options, &blk); end 87 | 88 | # source://parallel//lib/parallel.rb#390 89 | def work_in_ractors(job_factory, options); end 90 | 91 | # source://parallel//lib/parallel.rb#365 92 | def work_in_threads(job_factory, options, &block); end 93 | 94 | # source://parallel//lib/parallel.rb#524 95 | def worker(job_factory, options, &block); end 96 | end 97 | end 98 | 99 | # source://parallel//lib/parallel.rb#14 100 | class Parallel::Break < ::StandardError 101 | # @return [Break] a new instance of Break 102 | # 103 | # source://parallel//lib/parallel.rb#17 104 | def initialize(value = T.unsafe(nil)); end 105 | 106 | # Returns the value of attribute value. 107 | # 108 | # source://parallel//lib/parallel.rb#15 109 | def value; end 110 | end 111 | 112 | # source://parallel//lib/parallel.rb#11 113 | class Parallel::DeadWorker < ::StandardError; end 114 | 115 | # source://parallel//lib/parallel.rb#35 116 | class Parallel::ExceptionWrapper 117 | # @return [ExceptionWrapper] a new instance of ExceptionWrapper 118 | # 119 | # source://parallel//lib/parallel.rb#38 120 | def initialize(exception); end 121 | 122 | # Returns the value of attribute exception. 123 | # 124 | # source://parallel//lib/parallel.rb#36 125 | def exception; end 126 | end 127 | 128 | # source://parallel//lib/parallel.rb#101 129 | class Parallel::JobFactory 130 | # @return [JobFactory] a new instance of JobFactory 131 | # 132 | # source://parallel//lib/parallel.rb#102 133 | def initialize(source, mutex); end 134 | 135 | # source://parallel//lib/parallel.rb#110 136 | def next; end 137 | 138 | # generate item that is sent to workers 139 | # just index is faster + less likely to blow up with unserializable errors 140 | # 141 | # source://parallel//lib/parallel.rb#139 142 | def pack(item, index); end 143 | 144 | # source://parallel//lib/parallel.rb#129 145 | def size; end 146 | 147 | # unpack item that is sent to workers 148 | # 149 | # source://parallel//lib/parallel.rb#144 150 | def unpack(data); end 151 | 152 | private 153 | 154 | # @return [Boolean] 155 | # 156 | # source://parallel//lib/parallel.rb#150 157 | def producer?; end 158 | 159 | # source://parallel//lib/parallel.rb#154 160 | def queue_wrapper(array); end 161 | end 162 | 163 | # source://parallel//lib/parallel.rb#23 164 | class Parallel::Kill < ::Parallel::Break; end 165 | 166 | # TODO: inline this method into parallel.rb and kill physical_processor_count in next major release 167 | # 168 | # source://parallel//lib/parallel/processor_count.rb#4 169 | module Parallel::ProcessorCount 170 | # Number of physical processor cores on the current system. 171 | # 172 | # source://parallel//lib/parallel/processor_count.rb#12 173 | def physical_processor_count; end 174 | 175 | # Number of processors seen by the OS, used for process scheduling 176 | # 177 | # source://parallel//lib/parallel/processor_count.rb#6 178 | def processor_count; end 179 | end 180 | 181 | # source://parallel//lib/parallel.rb#9 182 | Parallel::Stop = T.let(T.unsafe(nil), Object) 183 | 184 | # source://parallel//lib/parallel.rb#26 185 | class Parallel::UndumpableException < ::StandardError 186 | # @return [UndumpableException] a new instance of UndumpableException 187 | # 188 | # source://parallel//lib/parallel.rb#29 189 | def initialize(original); end 190 | 191 | # Returns the value of attribute backtrace. 192 | # 193 | # source://parallel//lib/parallel.rb#27 194 | def backtrace; end 195 | end 196 | 197 | # source://parallel//lib/parallel.rb#159 198 | class Parallel::UserInterruptHandler 199 | class << self 200 | # source://parallel//lib/parallel.rb#184 201 | def kill(thing); end 202 | 203 | # kill all these pids or threads if user presses Ctrl+c 204 | # 205 | # source://parallel//lib/parallel.rb#164 206 | def kill_on_ctrl_c(pids, options); end 207 | 208 | private 209 | 210 | # source://parallel//lib/parallel.rb#208 211 | def restore_interrupt(old, signal); end 212 | 213 | # source://parallel//lib/parallel.rb#193 214 | def trap_interrupt(signal); end 215 | end 216 | end 217 | 218 | # source://parallel//lib/parallel.rb#160 219 | Parallel::UserInterruptHandler::INTERRUPT_SIGNAL = T.let(T.unsafe(nil), Symbol) 220 | 221 | # source://parallel//lib/parallel/version.rb#3 222 | Parallel::VERSION = T.let(T.unsafe(nil), String) 223 | 224 | # source://parallel//lib/parallel/version.rb#3 225 | Parallel::Version = T.let(T.unsafe(nil), String) 226 | 227 | # source://parallel//lib/parallel.rb#54 228 | class Parallel::Worker 229 | # @return [Worker] a new instance of Worker 230 | # 231 | # source://parallel//lib/parallel.rb#58 232 | def initialize(read, write, pid); end 233 | 234 | # might be passed to started_processes and simultaneously closed by another thread 235 | # when running in isolation mode, so we have to check if it is closed before closing 236 | # 237 | # source://parallel//lib/parallel.rb#71 238 | def close_pipes; end 239 | 240 | # Returns the value of attribute pid. 241 | # 242 | # source://parallel//lib/parallel.rb#55 243 | def pid; end 244 | 245 | # Returns the value of attribute read. 246 | # 247 | # source://parallel//lib/parallel.rb#55 248 | def read; end 249 | 250 | # source://parallel//lib/parallel.rb#64 251 | def stop; end 252 | 253 | # Returns the value of attribute thread. 254 | # 255 | # source://parallel//lib/parallel.rb#56 256 | def thread; end 257 | 258 | # Sets the attribute thread 259 | # 260 | # @param value the value to set the attribute thread to. 261 | # 262 | # source://parallel//lib/parallel.rb#56 263 | def thread=(_arg0); end 264 | 265 | # source://parallel//lib/parallel.rb#76 266 | def work(data); end 267 | 268 | # Returns the value of attribute write. 269 | # 270 | # source://parallel//lib/parallel.rb#55 271 | def write; end 272 | 273 | private 274 | 275 | # source://parallel//lib/parallel.rb#94 276 | def wait; end 277 | end 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Combo](./doc/combo.svg)](https://combohr.com) 2 | 3 |
4 | Table of Contents 5 | 6 | 7 | 1. [Installation](#installation) 8 | 1. [Configuration](#configuration) 9 | 1. [Events](#events) 10 | 1. [Database Operations](#database-operations) 11 | 1. [Merging Changesets](#merging-changesets) 12 | 1. [Push!](#push) 13 | 1. [Testing](#testing-) 14 | 1. [Sorbet](#sorbet) 15 | 1. [Example](#example) 16 | 1. [But why all these classes?](#but-why-all-these-classes) 17 | 18 |
19 | 20 | 21 | # Changeset 22 | 23 | The changeset contains all database operations and events of a command. 24 | 25 | The point of the Changeset is to delay the moment you persist until the end of a chain of method calls. 26 | 27 | The main reasons are: 28 | - use the shortest database transactions possible (holding transactions leads to many errors, nested transactions as well) 29 | - trigger necessary events once all data is persisted (jobs fail if started before transaction ends) 30 | 31 | Whatever the way you organize your code (plain methods, service objects...), you can leverage the changesets. 32 | 33 | --- 34 | 35 | It helped us solve complex use cases at [Combo](https://combohr.com) where some workflows overlapped. 36 | 37 | We had *long running transactions*, *duplicated workers* and needed a **simple**, **testable** yet **robust** way to write our persistence layer code. 38 | 39 | ## Installation 40 | 41 | ```ruby 42 | git_source(:github) { |project| File.join("https://github.com", "#{project}.git") } 43 | gem "changeset", github: "apneadiving/changeset" 44 | ``` 45 | 46 | ## Configuration 47 | One configuration is needed to use the gem: tell it how to use database transactions: 48 | 49 | ```ruby 50 | Changeset.configure do |config| 51 | config.db_transaction_wrapper = ->(&block) { 52 | ApplicationRecord.transaction do 53 | block.call 54 | end 55 | } 56 | end 57 | ``` 58 | 59 | ## Events 60 | 61 | They are meant to trigger only async processes: 62 | - background jobs 63 | - AMQP 64 | - KAFKA 65 | - ... 66 | 67 | Events have to be registered in a class to be used later: 68 | 69 | ```ruby 70 | class EventsCatalog 71 | KNOWN_EVENTS = [:planning_updated] 72 | def dispatch(event) 73 | send(event.name, event) 74 | end 75 | 76 | def known_event?(event_name) 77 | KNOWN_EVENTS.include?(event_name) 78 | end 79 | 80 | private 81 | 82 | def planning_updated(event) 83 | # Trigger workers or any async processes. 84 | # One event can mean many workers etc, your call. 85 | # From here you can use event.payload 86 | end 87 | end 88 | ``` 89 | 90 | There are two ways to add events to a changeset: 91 | ```ruby 92 | changeset = Changeset.new(EventsCatalog) 93 | # if you know all params at the time you add the event: 94 | changeset.add_event( 95 | :planning_udpated, 96 | { week: "2022W47" } 97 | ) 98 | # if you do not know all params at the time you add the event, 99 | # but know it will be populated once database operations are committed 100 | changeset.add_event( 101 | :planning_udpated, 102 | -> { { week: some_object.week_identifier } } 103 | ) 104 | ``` 105 | 106 | For now there is a dedup mechanism to avoid same events to be dispatched several times. Indeed some actions may add same events in their own context and afeter traversing them all we know it is not necessary. 107 | 108 | The unicity is based on: 109 | - the event catalog class name 110 | - the name of the event 111 | - the payload of the event 112 | 113 | ## Database Operations 114 | 115 | They are meant to be objects containing the relevant logic to call the database and commit persistence operations. 116 | These classes must match the PersistenceInterface: respond to `call`. 117 | 118 | You can create any depending on your needs: create, update, delete, bulk upsert... 119 | 120 | A very basic example is: 121 | ```ruby 122 | class BasicPersistenceHandler 123 | def initialize(active_record_object) 124 | @active_record_object = active_record_object 125 | end 126 | 127 | def call 128 | @active_record_object.save! 129 | end 130 | end 131 | ``` 132 | 133 | You can then add database operations to the changeset. 134 | ```ruby 135 | changeset = Changeset.new # notice we didnt pass an event catalog because we wont use events 136 | 137 | user = User.new(params) 138 | 139 | changeset.add_db_operation( 140 | BasicPersistenceHandler.new(user) 141 | ) 142 | ``` 143 | 144 | If you do not need them to be reused, just use a lambda: 145 | ``` 146 | user = User.new(params) 147 | 148 | changeset.add_db_operation( 149 | -> { user.save! } 150 | ) 151 | ``` 152 | 153 | Database operations will then be commited in the order they were added to the changeset. 154 | 155 | ## Merging changesets 156 | 157 | The very point of changesets is they can be merged. 158 | 159 | On merge: 160 | - parent changeset concatenates all db operations of its child 161 | - parent changeset merges all events from its child 162 | 163 | ```ruby 164 | parent_changeset = Changeset.new(EventsCatalog) 165 | parent_changeset 166 | .add_db_operations( 167 | db_operation1, 168 | db_operation2 169 | ) 170 | .add_event(:planning_updated, { week: "2022W47" }) 171 | 172 | child_changeset = Changeset.new(EventsCatalog) 173 | .add_db_operations( 174 | db_operation3, 175 | db_operation4 176 | ) 177 | .add_event(:planning_updated, { week: "2022W47" }) 178 | .add_event(:planning_updated, { week: "2022W48" }) 179 | 180 | parent_changeset.merge_child(child_changeset) 181 | 182 | parent_changeset 183 | .add_db_operation( 184 | db_operation5 185 | ) 186 | 187 | # - db operations will be in order 1, 2, 3, 4, 5 188 | # - only one planning_updated event will be dispatched with param {week: "2022W47"} 189 | # - only one planning_updated event will be dispatched with param {week: "2022W48"} 190 | ``` 191 | 192 | ## Push! 193 | 194 | At the end of the calls chain, it is the appropriate time to persist data and trigger events: 195 | 196 | ```ruby 197 | changeset.push! 198 | ``` 199 | 200 | This will: 201 | - persist all database operations in a single transaction 202 | - then trigger all events (outside the transaction) 203 | 204 | ## Testing ⚡ 205 | 206 | A very convenient aspect of using changesets in you can run multiple scenarios without touching the database. 207 | 208 | In the end you can compare the actual changeset you get against your expected one. 209 | 210 | This requires to use real classes for persistence and implement `==` in these. You cannot really get procs to compare for equality. 211 | 212 | ## Sorbet 213 | 214 | This gem is typed with Sorbet and contains rbi definitions. 215 | 216 | ## Example 217 | 218 | Completely inspired from a discussion on Twitter you can find here: https://twitter.com/davetron5000/status/1575512016504164352 219 | 220 | We need to be fault tolerant in cases like below: 221 | 222 | ```ruby 223 | def charge(customer, amount_cents) 224 | # These two create! calls must 225 | # either both succeed or both fail 226 | invoice = Invoice.create!( 227 | customer: customer, 228 | amount_cents: amount_cents, 229 | ) 230 | charge = Charge.create!( 231 | invoice: invoice, 232 | amount_cents: amount_cents, 233 | ) 234 | ChargeJob.perform_async(charge.id) 235 | end 236 | ``` 237 | 238 | It generally goes down to adding a transaction: 239 | 240 | ```ruby 241 | def charge(customer, amount_cents) 242 | ActiveRecord::Base.transaction do 243 | invoice = Invoice.create!( 244 | customer: customer, 245 | amount_cents: amount_cents, 246 | ) 247 | charge = Charge.create!( 248 | invoice: invoice, 249 | amount_cents: amount_cents, 250 | ) 251 | end 252 | # we can argue whether or not this should go inside the transaction... 253 | ChargeJob.perform_async(charge.id) 254 | end 255 | ``` 256 | 257 | You soon need to reuse this method in a larger context, and you now need to nest transactions: 258 | 259 | ```ruby 260 | def appointment_attended(appointment) 261 | ActiveRecord::Base.transaction(requires_new: true) do 262 | copay_cents = appointment.service.copay_cents 263 | charge = charge(appointment.customer, copay_cents) 264 | 265 | # create_insurance_claim would create yet another nested transaction 266 | insurance_claim = create_insurance_claim(appointment, copay: charge) 267 | end 268 | # again, triggering the job here is maybe not the best option 269 | SubmitToInsuranceJob.perform_async(insurance_claim.id) 270 | end 271 | 272 | def charge(customer, amount_cents) 273 | ActiveRecord::Base.transaction(requires_new: true) do 274 | invoice = Invoice.create!( 275 | customer: customer, 276 | amount_cents: amount_cents, 277 | ) 278 | charge = Charge.create!( 279 | invoice: invoice, 280 | amount_cents: amount_cents, 281 | ) 282 | end 283 | # we can argue whether or not this should go inside the transaction... 284 | ChargeJob.perform_async(charge.id) 285 | end 286 | ``` 287 | 288 | As you can tell, we are putting more and more weight on the transation. 289 | Holding a transaction takes a huge toll on your database opening the door to multiple weird errors. 290 | The most common ones being: 291 | - timeouts 292 | - locking errors 293 | - background job failing because they are unable to find database records (they can actually be trigerred before the transaction ended) 294 | 295 | --- 296 | 297 | Now with the Changeset: 298 | 299 | ```ruby 300 | # we need a catalog 301 | class EventsCatalog 302 | KNOWN_EVENTS = [:customer_charged, :insurance_claim_created] 303 | def dispatch(event) 304 | send(event.name, event) 305 | end 306 | 307 | def known_event?(event_name) 308 | KNOWN_EVENTS.include?(event_name) 309 | end 310 | 311 | private 312 | 313 | def customer_charged(event) 314 | ChargeJob.perform_async(event.payload[:id]) 315 | end 316 | 317 | def insurance_claim_created(event) 318 | SubmitToInsuranceJob.perform_async(event.payload[:id]) 319 | end 320 | end 321 | 322 | def appointment_attended(appointment) 323 | Changeset.new(EventsCatalog).yield_self do |changeset| 324 | copay_cents = appointment.service.copay_cents 325 | 326 | new_charge, charge_changeset = charge(appointment.customer, copay_cents) 327 | changeset.merge_child(charge_changeset) 328 | 329 | insurance_claim, insurance_claim_changeset = create_insurance_claim(appointment, copay: new_charge) 330 | changeset.merge_child(insurance_claim_changeset) 331 | 332 | changeset.add_event( 333 | :insurance_claim_created, 334 | -> { { id: insurance_claim.id } } 335 | ) 336 | end 337 | end 338 | 339 | def charge(customer, amount_cents) 340 | Changeset.new(EventsCatalog).yield_self do |changeset| 341 | invoice = Invoice.new( 342 | customer: customer, 343 | amount_cents: amount_cents 344 | ) 345 | charge = Charge.new( 346 | invoice: invoice, 347 | amount_cents: amount_cents 348 | ) 349 | 350 | changeset 351 | .add_db_operations( 352 | -> { invoice.save! }, 353 | -> { charge.save! } 354 | ) 355 | .add_event( 356 | :customer_charged, 357 | -> { { id: charge.id } } 358 | ) 359 | 360 | [charge, changeset] 361 | end 362 | end 363 | 364 | # usage 365 | changeset = appointment_attended(appointment) 366 | changeset.push! 367 | ``` 368 | 369 | One database transaction, workers triggered at the appropriate time. 370 | 371 | ## But why all these classes? 372 | 373 | I realized this kind of structure was necessary through my job at combohr.com, where we heavily use Domain Driven Design. 374 | 375 | Because we do not use ActiveRecord within the domain (no objects, no query, no nothing), we need a way to bridge back from our own Ruby object to the persistence layer. This is where Persistence classes came into play. 376 | 377 | Anyway it is a good habit to have a facade to decouple your intent and the actual implementation. 378 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/yard-sorbet@0.7.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `yard-sorbet` gem. 5 | # Please instead update this file by running `bin/tapioca gem yard-sorbet`. 6 | 7 | class YARD::Handlers::Ruby::ClassHandler < ::YARD::Handlers::Ruby::Base 8 | include ::YARDSorbet::Handlers::StructClassHandler 9 | end 10 | 11 | # Types are documentation 12 | # 13 | # source://yard-sorbet//lib/yard-sorbet/version.rb#5 14 | module YARDSorbet; end 15 | 16 | # Extract & re-add directives to a docstring 17 | # 18 | # source://yard-sorbet//lib/yard-sorbet/directives.rb#6 19 | module YARDSorbet::Directives 20 | class << self 21 | # source://yard-sorbet//lib/yard-sorbet/directives.rb#21 22 | sig { params(docstring: ::String, directives: T::Array[::String]).void } 23 | def add_directives(docstring, directives); end 24 | 25 | # source://yard-sorbet//lib/yard-sorbet/directives.rb#10 26 | sig { params(docstring: T.nilable(::String)).returns([::YARD::Docstring, T::Array[::String]]) } 27 | def extract_directives(docstring); end 28 | end 29 | end 30 | 31 | # Custom YARD Handlers 32 | # 33 | # @see https://rubydoc.info/gems/yard/YARD/Handlers/Base YARD Base Handler documentation 34 | # 35 | # source://yard-sorbet//lib/yard-sorbet/handlers.rb#7 36 | module YARDSorbet::Handlers; end 37 | 38 | # Apllies an `@abstract` tag to `abstract!`/`interface!` modules (if not alerady present). 39 | # 40 | # source://yard-sorbet//lib/yard-sorbet/handlers/abstract_dsl_handler.rb#7 41 | class YARDSorbet::Handlers::AbstractDSLHandler < ::YARD::Handlers::Ruby::Base 42 | # source://yard-sorbet//lib/yard-sorbet/handlers/abstract_dsl_handler.rb#21 43 | sig { void } 44 | def process; end 45 | end 46 | 47 | # Extra text for class namespaces 48 | # 49 | # source://yard-sorbet//lib/yard-sorbet/handlers/abstract_dsl_handler.rb#18 50 | YARDSorbet::Handlers::AbstractDSLHandler::CLASS_TAG_TEXT = T.let(T.unsafe(nil), String) 51 | 52 | # The text accompanying the `@abstract` tag. 53 | # 54 | # @see https://github.com/lsegal/yard/blob/main/templates/default/docstring/html/abstract.erb The `@abstract` tag template 55 | # 56 | # source://yard-sorbet//lib/yard-sorbet/handlers/abstract_dsl_handler.rb#16 57 | YARDSorbet::Handlers::AbstractDSLHandler::TAG_TEXT = T.let(T.unsafe(nil), String) 58 | 59 | # Handle `enums` calls, registering enum values as constants 60 | # 61 | # source://yard-sorbet//lib/yard-sorbet/handlers/enums_handler.rb#7 62 | class YARDSorbet::Handlers::EnumsHandler < ::YARD::Handlers::Ruby::Base 63 | # source://yard-sorbet//lib/yard-sorbet/handlers/enums_handler.rb#14 64 | sig { void } 65 | def process; end 66 | 67 | private 68 | 69 | # source://yard-sorbet//lib/yard-sorbet/handlers/enums_handler.rb#29 70 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(T::Boolean) } 71 | def const_assign_node?(node); end 72 | end 73 | 74 | # Extends any modules included via `mixes_in_class_methods` 75 | # 76 | # @see https://sorbet.org/docs/abstract#interfaces-and-the-included-hook Sorbet `mixes_in_class_methods` documentation 77 | # 78 | # source://yard-sorbet//lib/yard-sorbet/handlers/include_handler.rb#9 79 | class YARDSorbet::Handlers::IncludeHandler < ::YARD::Handlers::Ruby::Base 80 | # source://yard-sorbet//lib/yard-sorbet/handlers/include_handler.rb#16 81 | sig { void } 82 | def process; end 83 | 84 | private 85 | 86 | # source://yard-sorbet//lib/yard-sorbet/handlers/include_handler.rb#30 87 | sig { returns(::YARD::CodeObjects::NamespaceObject) } 88 | def included_in; end 89 | end 90 | 91 | # Tracks modules that invoke `mixes_in_class_methods` for use in {IncludeHandler} 92 | # 93 | # @see https://sorbet.org/docs/abstract#interfaces-and-the-included-hook Sorbet `mixes_in_class_methods` documentation 94 | # 95 | # source://yard-sorbet//lib/yard-sorbet/handlers/mixes_in_class_methods_handler.rb#9 96 | class YARDSorbet::Handlers::MixesInClassMethodsHandler < ::YARD::Handlers::Ruby::Base 97 | # source://yard-sorbet//lib/yard-sorbet/handlers/mixes_in_class_methods_handler.rb#23 98 | sig { void } 99 | def process; end 100 | 101 | class << self 102 | # source://yard-sorbet//lib/yard-sorbet/handlers/mixes_in_class_methods_handler.rb#18 103 | sig { params(code_obj: ::String).returns(T.nilable(::String)) } 104 | def mixed_in_class_methods(code_obj); end 105 | end 106 | end 107 | 108 | # A YARD Handler for Sorbet type declarations 109 | # 110 | # source://yard-sorbet//lib/yard-sorbet/handlers/sig_handler.rb#7 111 | class YARDSorbet::Handlers::SigHandler < ::YARD::Handlers::Ruby::Base 112 | # Swap the method definition docstring and the sig docstring. 113 | # Parse relevant parts of the `sig` and include them as well. 114 | # 115 | # source://yard-sorbet//lib/yard-sorbet/handlers/sig_handler.rb#20 116 | sig { void } 117 | def process; end 118 | 119 | private 120 | 121 | # source://yard-sorbet//lib/yard-sorbet/handlers/sig_handler.rb#52 122 | sig do 123 | params( 124 | method_node: ::YARD::Parser::Ruby::AstNode, 125 | node: ::YARD::Parser::Ruby::AstNode, 126 | docstring: ::YARD::Docstring 127 | ).void 128 | end 129 | def parse_params(method_node, node, docstring); end 130 | 131 | # source://yard-sorbet//lib/yard-sorbet/handlers/sig_handler.rb#64 132 | sig { params(node: ::YARD::Parser::Ruby::AstNode, docstring: ::YARD::Docstring).void } 133 | def parse_return(node, docstring); end 134 | 135 | # source://yard-sorbet//lib/yard-sorbet/handlers/sig_handler.rb#32 136 | sig { params(method_node: ::YARD::Parser::Ruby::AstNode, docstring: ::YARD::Docstring).void } 137 | def parse_sig(method_node, docstring); end 138 | end 139 | 140 | # These node types attached to sigs represent attr_* declarations 141 | # 142 | # source://yard-sorbet//lib/yard-sorbet/handlers/sig_handler.rb#14 143 | YARDSorbet::Handlers::SigHandler::ATTR_NODE_TYPES = T.let(T.unsafe(nil), Array) 144 | 145 | # Class-level handler that folds all `const` and `prop` declarations into the constructor documentation 146 | # this needs to be injected as a module otherwise the default Class handler will overwrite documentation 147 | # 148 | # @note this module is included in `YARD::Handlers::Ruby::ClassHandler` 149 | # 150 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_class_handler.rb#10 151 | module YARDSorbet::Handlers::StructClassHandler 152 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_class_handler.rb#14 153 | sig { void } 154 | def process; end 155 | 156 | private 157 | 158 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_class_handler.rb#50 159 | sig do 160 | params( 161 | object: ::YARD::CodeObjects::MethodObject, 162 | props: T::Array[::YARDSorbet::TStructProp], 163 | docstring: ::YARD::Docstring, 164 | directives: T::Array[::String] 165 | ).void 166 | end 167 | def decorate_t_struct_init(object, props, docstring, directives); end 168 | 169 | # Create a virtual `initialize` method with all the `prop`/`const` arguments 170 | # 171 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_class_handler.rb#30 172 | sig { params(props: T::Array[::YARDSorbet::TStructProp], class_ns: ::YARD::CodeObjects::ClassObject).void } 173 | def process_t_struct_props(props, class_ns); end 174 | 175 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_class_handler.rb#60 176 | sig { params(props: T::Array[::YARDSorbet::TStructProp]).returns(T::Array[[::String, T.nilable(::String)]]) } 177 | def to_object_parameters(props); end 178 | end 179 | 180 | # Handles all `const`/`prop` calls, creating accessor methods, and compiles them for later usage at the class level 181 | # in creating a constructor 182 | # 183 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#8 184 | class YARDSorbet::Handlers::StructPropHandler < ::YARD::Handlers::Ruby::Base 185 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#15 186 | sig { void } 187 | def process; end 188 | 189 | private 190 | 191 | # Add the source and docstring to the method object 192 | # 193 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#28 194 | sig { params(object: ::YARD::CodeObjects::MethodObject, prop: ::YARDSorbet::TStructProp).void } 195 | def decorate_object(object, prop); end 196 | 197 | # Get the default prop value 198 | # 199 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#39 200 | sig { returns(T.nilable(::String)) } 201 | def default_value; end 202 | 203 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#44 204 | sig { params(name: ::String).returns(::YARDSorbet::TStructProp) } 205 | def make_prop(name); end 206 | 207 | # Register the field explicitly as an attribute. 208 | # While `const` attributes are immutable, `prop` attributes may be reassigned. 209 | # 210 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#57 211 | sig { params(object: ::YARD::CodeObjects::MethodObject, name: ::String).void } 212 | def register_attrs(object, name); end 213 | 214 | # Store the prop for use in the constructor definition 215 | # 216 | # source://yard-sorbet//lib/yard-sorbet/handlers/struct_prop_handler.rb#65 217 | sig { params(prop: ::YARDSorbet::TStructProp).void } 218 | def update_state(prop); end 219 | end 220 | 221 | # Helper methods for working with `YARD` AST Nodes 222 | # 223 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#6 224 | module YARDSorbet::NodeUtils 225 | class << self 226 | # Traverse AST nodes in breadth-first order 227 | # 228 | # @note This will skip over some node types. 229 | # @yield [YARD::Parser::Ruby::AstNode] 230 | # 231 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#22 232 | sig do 233 | params( 234 | node: ::YARD::Parser::Ruby::AstNode, 235 | _blk: T.proc.params(n: ::YARD::Parser::Ruby::AstNode).void 236 | ).void 237 | end 238 | def bfs_traverse(node, &_blk); end 239 | 240 | # Gets the node that a sorbet `sig` can be attached do, bypassing visisbility modifiers and the like 241 | # 242 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#34 243 | sig do 244 | params( 245 | node: ::YARD::Parser::Ruby::AstNode 246 | ).returns(T.any(::YARD::Parser::Ruby::MethodCallNode, ::YARD::Parser::Ruby::MethodDefinitionNode)) 247 | end 248 | def get_method_node(node); end 249 | 250 | # Find and return the adjacent node (ascending) 251 | # 252 | # @raise [IndexError] if the node does not have an adjacent sibling (ascending) 253 | # 254 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#48 255 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(::YARD::Parser::Ruby::AstNode) } 256 | def sibling_node(node); end 257 | end 258 | end 259 | 260 | # Command node types that can have type signatures 261 | # 262 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#10 263 | YARDSorbet::NodeUtils::ATTRIBUTE_METHODS = T.let(T.unsafe(nil), Array) 264 | 265 | # Skip these method contents during BFS node traversal, they can have their own nested types via `T.Proc` 266 | # 267 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#12 268 | YARDSorbet::NodeUtils::SKIP_METHOD_CONTENTS = T.let(T.unsafe(nil), Array) 269 | 270 | # Node types that can have type signatures 271 | # 272 | # source://yard-sorbet//lib/yard-sorbet/node_utils.rb#14 273 | YARDSorbet::NodeUtils::SigableNode = T.type_alias { T.any(::YARD::Parser::Ruby::MethodCallNode, ::YARD::Parser::Ruby::MethodDefinitionNode) } 274 | 275 | # Translate `sig` type syntax to `YARD` type syntax. 276 | # 277 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#6 278 | module YARDSorbet::SigToYARD 279 | class << self 280 | # @see https://yardoc.org/types.html 281 | # 282 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#21 283 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(T::Array[::String]) } 284 | def convert(node); end 285 | 286 | private 287 | 288 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#54 289 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(::String) } 290 | def build_generic_type(node); end 291 | 292 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#63 293 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(T::Array[::String]) } 294 | def convert_aref(node); end 295 | 296 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#75 297 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns([::String]) } 298 | def convert_array(node); end 299 | 300 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#83 301 | sig { params(node: ::YARD::Parser::Ruby::MethodCallNode).returns(T::Array[::String]) } 302 | def convert_call(node); end 303 | 304 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#88 305 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns([::String]) } 306 | def convert_collection(node); end 307 | 308 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#95 309 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns([::String]) } 310 | def convert_hash(node); end 311 | 312 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#103 313 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(T::Array[::String]) } 314 | def convert_list(node); end 315 | 316 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#27 317 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(T::Array[::String]) } 318 | def convert_node(node); end 319 | 320 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#36 321 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns(T::Array[::String]) } 322 | def convert_node_type(node); end 323 | 324 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#108 325 | sig { params(node_source: ::String).returns([::String]) } 326 | def convert_ref(node_source); end 327 | 328 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#113 329 | sig { params(node: ::YARD::Parser::Ruby::MethodCallNode).returns(T::Array[::String]) } 330 | def convert_t_method(node); end 331 | 332 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#125 333 | sig { params(node: ::YARD::Parser::Ruby::AstNode).returns([::String]) } 334 | def convert_unknown(node); end 335 | end 336 | end 337 | 338 | # source://yard-sorbet//lib/yard-sorbet/sig_to_yard.rb#9 339 | YARDSorbet::SigToYARD::REF_TYPES = T.let(T.unsafe(nil), Hash) 340 | 341 | # Used to store the details of a `T::Struct` `prop` definition 342 | # 343 | # source://yard-sorbet//lib/yard-sorbet/t_struct_prop.rb#6 344 | class YARDSorbet::TStructProp < ::T::Struct 345 | const :default, T.nilable(::String) 346 | const :doc, ::String 347 | const :prop_name, ::String 348 | const :source, ::String 349 | const :types, T::Array[::String] 350 | 351 | class << self 352 | # source://sorbet-runtime/0.5.10346/lib/types/struct.rb#13 353 | def inherited(s); end 354 | end 355 | end 356 | 357 | # Helper methods for working with `YARD` tags 358 | # 359 | # source://yard-sorbet//lib/yard-sorbet/tag_utils.rb#6 360 | module YARDSorbet::TagUtils 361 | class << self 362 | # source://yard-sorbet//lib/yard-sorbet/tag_utils.rb#13 363 | sig do 364 | params( 365 | docstring: ::YARD::Docstring, 366 | tag_name: ::String, 367 | name: T.nilable(::String) 368 | ).returns(T.nilable(::YARD::Tags::Tag)) 369 | end 370 | def find_tag(docstring, tag_name, name); end 371 | 372 | # Create or update a `YARD` tag with type information 373 | # 374 | # source://yard-sorbet//lib/yard-sorbet/tag_utils.rb#27 375 | sig do 376 | params( 377 | docstring: ::YARD::Docstring, 378 | tag_name: ::String, 379 | types: T.nilable(T::Array[::String]), 380 | name: T.nilable(::String), 381 | text: ::String 382 | ).void 383 | end 384 | def upsert_tag(docstring, tag_name, types = T.unsafe(nil), name = T.unsafe(nil), text = T.unsafe(nil)); end 385 | end 386 | end 387 | 388 | # {https://rubygems.org/gems/yard-sorbet Version history} 389 | # 390 | # source://yard-sorbet//lib/yard-sorbet/version.rb#7 391 | YARDSorbet::VERSION = T.let(T.unsafe(nil), String) 392 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/ast@2.4.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `ast` gem. 5 | # Please instead update this file by running `bin/tapioca gem ast`. 6 | 7 | # {AST} is a library for manipulating abstract syntax trees. 8 | # 9 | # It embraces immutability; each AST node is inherently frozen at 10 | # creation, and updating a child node requires recreating that node 11 | # and its every parent, recursively. 12 | # This is a design choice. It does create some pressure on 13 | # garbage collector, but completely eliminates all concurrency 14 | # and aliasing problems. 15 | # 16 | # See also {AST::Node}, {AST::Processor::Mixin} and {AST::Sexp} for 17 | # additional recommendations and design patterns. 18 | # 19 | # source://ast//lib/ast.rb#13 20 | module AST; end 21 | 22 | # Node is an immutable class, instances of which represent abstract 23 | # syntax tree nodes. It combines semantic information (i.e. anything 24 | # that affects the algorithmic properties of a program) with 25 | # meta-information (line numbers or compiler intermediates). 26 | # 27 | # Notes on inheritance 28 | # ==================== 29 | # 30 | # The distinction between semantics and metadata is important. Complete 31 | # semantic information should be contained within just the {#type} and 32 | # {#children} of a Node instance; in other words, if an AST was to be 33 | # stripped of all meta-information, it should remain a valid AST which 34 | # could be successfully processed to yield a result with the same 35 | # algorithmic properties. 36 | # 37 | # Thus, Node should never be inherited in order to define methods which 38 | # affect or return semantic information, such as getters for `class_name`, 39 | # `superclass` and `body` in the case of a hypothetical `ClassNode`. The 40 | # correct solution is to use a generic Node with a {#type} of `:class` 41 | # and three children. See also {Processor} for tips on working with such 42 | # ASTs. 43 | # 44 | # On the other hand, Node can and should be inherited to define 45 | # application-specific metadata (see also {#initialize}) or customize the 46 | # printing format. It is expected that an application would have one or two 47 | # such classes and use them across the entire codebase. 48 | # 49 | # The rationale for this pattern is extensibility and maintainability. 50 | # Unlike static ones, dynamic languages do not require the presence of a 51 | # predefined, rigid structure, nor does it improve dispatch efficiency, 52 | # and while such a structure can certainly be defined, it does not add 53 | # any value but incurs a maintaining cost. 54 | # For example, extending the AST even with a transformation-local 55 | # temporary node type requires making globally visible changes to 56 | # the codebase. 57 | # 58 | # source://ast//lib/ast/node.rb#40 59 | class AST::Node 60 | # Constructs a new instance of Node. 61 | # 62 | # The arguments `type` and `children` are converted with `to_sym` and 63 | # `to_a` respectively. Additionally, the result of converting `children` 64 | # is frozen. While mutating the arguments is generally considered harmful, 65 | # the most common case is to pass an array literal to the constructor. If 66 | # your code does not expect the argument to be frozen, use `#dup`. 67 | # 68 | # The `properties` hash is passed to {#assign_properties}. 69 | # 70 | # @return [Node] a new instance of Node 71 | # 72 | # source://ast//lib/ast/node.rb#72 73 | def initialize(type, children = T.unsafe(nil), properties = T.unsafe(nil)); end 74 | 75 | # Concatenates `array` with `children` and returns the resulting node. 76 | # 77 | # @return [AST::Node] 78 | # 79 | # source://ast//lib/ast/node.rb#168 80 | def +(array); end 81 | 82 | # Appends `element` to `children` and returns the resulting node. 83 | # 84 | # @return [AST::Node] 85 | # 86 | # source://ast//lib/ast/node.rb#177 87 | def <<(element); end 88 | 89 | # Compares `self` to `other`, possibly converting with `to_ast`. Only 90 | # `type` and `children` are compared; metadata is deliberately ignored. 91 | # 92 | # @return [Boolean] 93 | # 94 | # source://ast//lib/ast/node.rb#153 95 | def ==(other); end 96 | 97 | # Appends `element` to `children` and returns the resulting node. 98 | # 99 | # @return [AST::Node] 100 | # 101 | # source://ast//lib/ast/node.rb#177 102 | def append(element); end 103 | 104 | # Returns the children of this node. 105 | # The returned value is frozen. 106 | # The to_a alias is useful for decomposing nodes concisely. 107 | # For example: 108 | # 109 | # node = s(:gasgn, :$foo, s(:integer, 1)) 110 | # var_name, value = *node 111 | # p var_name # => :$foo 112 | # p value # => (integer 1) 113 | # 114 | # @return [Array] 115 | # 116 | # source://ast//lib/ast/node.rb#56 117 | def children; end 118 | 119 | # Nodes are already frozen, so there is no harm in returning the 120 | # current node as opposed to initializing from scratch and freezing 121 | # another one. 122 | # 123 | # @return self 124 | # 125 | # source://ast//lib/ast/node.rb#115 126 | def clone; end 127 | 128 | # Concatenates `array` with `children` and returns the resulting node. 129 | # 130 | # @return [AST::Node] 131 | # 132 | # source://ast//lib/ast/node.rb#168 133 | def concat(array); end 134 | 135 | # Enables matching for Node, where type is the first element 136 | # and the children are remaining items. 137 | # 138 | # @return [Array] 139 | # 140 | # source://ast//lib/ast/node.rb#253 141 | def deconstruct; end 142 | 143 | # Nodes are already frozen, so there is no harm in returning the 144 | # current node as opposed to initializing from scratch and freezing 145 | # another one. 146 | # 147 | # @return self 148 | # 149 | # source://ast//lib/ast/node.rb#115 150 | def dup; end 151 | 152 | # Test if other object is equal to 153 | # 154 | # @param other [Object] 155 | # @return [Boolean] 156 | # 157 | # source://ast//lib/ast/node.rb#85 158 | def eql?(other); end 159 | 160 | # Returns the precomputed hash value for this node 161 | # 162 | # @return [Fixnum] 163 | # 164 | # source://ast//lib/ast/node.rb#61 165 | def hash; end 166 | 167 | # Converts `self` to a s-expression ruby string. 168 | # The code return will recreate the node, using the sexp module s() 169 | # 170 | # @param indent [Integer] Base indentation level. 171 | # @return [String] 172 | # 173 | # source://ast//lib/ast/node.rb#211 174 | def inspect(indent = T.unsafe(nil)); end 175 | 176 | # Returns the children of this node. 177 | # The returned value is frozen. 178 | # The to_a alias is useful for decomposing nodes concisely. 179 | # For example: 180 | # 181 | # node = s(:gasgn, :$foo, s(:integer, 1)) 182 | # var_name, value = *node 183 | # p var_name # => :$foo 184 | # p value # => (integer 1) 185 | # 186 | # @return [Array] 187 | # 188 | # source://ast//lib/ast/node.rb#56 189 | def to_a; end 190 | 191 | # @return [AST::Node] self 192 | # 193 | # source://ast//lib/ast/node.rb#229 194 | def to_ast; end 195 | 196 | # Converts `self` to a pretty-printed s-expression. 197 | # 198 | # @param indent [Integer] Base indentation level. 199 | # @return [String] 200 | # 201 | # source://ast//lib/ast/node.rb#187 202 | def to_s(indent = T.unsafe(nil)); end 203 | 204 | # Converts `self` to a pretty-printed s-expression. 205 | # 206 | # @param indent [Integer] Base indentation level. 207 | # @return [String] 208 | # 209 | # source://ast//lib/ast/node.rb#187 210 | def to_sexp(indent = T.unsafe(nil)); end 211 | 212 | # Converts `self` to an Array where the first element is the type as a Symbol, 213 | # and subsequent elements are the same representation of its children. 214 | # 215 | # @return [Array] 216 | # 217 | # source://ast//lib/ast/node.rb#237 218 | def to_sexp_array; end 219 | 220 | # Returns the type of this node. 221 | # 222 | # @return [Symbol] 223 | # 224 | # source://ast//lib/ast/node.rb#43 225 | def type; end 226 | 227 | # Returns a new instance of Node where non-nil arguments replace the 228 | # corresponding fields of `self`. 229 | # 230 | # For example, `Node.new(:foo, [ 1, 2 ]).updated(:bar)` would yield 231 | # `(bar 1 2)`, and `Node.new(:foo, [ 1, 2 ]).updated(nil, [])` would 232 | # yield `(foo)`. 233 | # 234 | # If the resulting node would be identical to `self`, does nothing. 235 | # 236 | # @param type [Symbol, nil] 237 | # @param children [Array, nil] 238 | # @param properties [Hash, nil] 239 | # @return [AST::Node] 240 | # 241 | # source://ast//lib/ast/node.rb#133 242 | def updated(type = T.unsafe(nil), children = T.unsafe(nil), properties = T.unsafe(nil)); end 243 | 244 | protected 245 | 246 | # By default, each entry in the `properties` hash is assigned to 247 | # an instance variable in this instance of Node. A subclass should define 248 | # attribute readers for such variables. The values passed in the hash 249 | # are not frozen or whitelisted; such behavior can also be implemented 250 | # by subclassing Node and overriding this method. 251 | # 252 | # @return [nil] 253 | # 254 | # source://ast//lib/ast/node.rb#98 255 | def assign_properties(properties); end 256 | 257 | # Returns `@type` with all underscores replaced by dashes. This allows 258 | # to write symbol literals without quotes in Ruby sources and yet have 259 | # nicely looking s-expressions. 260 | # 261 | # @return [String] 262 | # 263 | # source://ast//lib/ast/node.rb#264 264 | def fancy_type; end 265 | 266 | private 267 | 268 | def original_dup; end 269 | end 270 | 271 | # This class includes {AST::Processor::Mixin}; however, it is 272 | # deprecated, since the module defines all of the behaviors that 273 | # the processor includes. Any new libraries should use 274 | # {AST::Processor::Mixin} instead of subclassing this. 275 | # 276 | # @deprecated Use {AST::Processor::Mixin} instead. 277 | # 278 | # source://ast//lib/ast/processor.rb#8 279 | class AST::Processor 280 | include ::AST::Processor::Mixin 281 | end 282 | 283 | # The processor module is a module which helps transforming one 284 | # AST into another. In a nutshell, the {#process} method accepts 285 | # a {Node} and dispatches it to a handler corresponding to its 286 | # type, and returns a (possibly) updated variant of the node. 287 | # 288 | # The processor module has a set of associated design patterns. 289 | # They are best explained with a concrete example. Let's define a 290 | # simple arithmetic language and an AST format for it: 291 | # 292 | # Terminals (AST nodes which do not have other AST nodes inside): 293 | # 294 | # * `(integer )`, 295 | # 296 | # Nonterminals (AST nodes with other nodes as children): 297 | # 298 | # * `(add )`, 299 | # * `(multiply )`, 300 | # * `(divide )`, 301 | # * `(negate )`, 302 | # * `(store )`: stores value of `` 303 | # into a variable named ``, 304 | # * `(load )`: loads value of a variable named 305 | # ``, 306 | # * `(each ...)`: computes each of the ``s and 307 | # prints the result. 308 | # 309 | # All AST nodes have the same Ruby class, and therefore they don't 310 | # know how to traverse themselves. (A solution which dynamically 311 | # checks the type of children is possible, but is slow and 312 | # error-prone.) So, a class including the module which knows how 313 | # to traverse the entire tree should be defined. Such classes 314 | # have a handler for each nonterminal node which recursively 315 | # processes children nodes: 316 | # 317 | # require 'ast' 318 | # 319 | # class ArithmeticsProcessor 320 | # include AST::Processor::Mixin 321 | # # This method traverses any binary operators such as (add) 322 | # # or (multiply). 323 | # def process_binary_op(node) 324 | # # Children aren't decomposed automatically; it is 325 | # # suggested to use Ruby multiple assignment expansion, 326 | # # as it is very convenient here. 327 | # left_expr, right_expr = *node 328 | # 329 | # # AST::Node#updated won't change node type if nil is 330 | # # passed as a first argument, which allows to reuse the 331 | # # same handler for multiple node types using `alias' 332 | # # (below). 333 | # node.updated(nil, [ 334 | # process(left_expr), 335 | # process(right_expr) 336 | # ]) 337 | # end 338 | # alias_method :on_add, :process_binary_op 339 | # alias_method :on_multiply, :process_binary_op 340 | # alias_method :on_divide, :process_binary_op 341 | # 342 | # def on_negate(node) 343 | # # It is also possible to use #process_all for more 344 | # # compact code if every child is a Node. 345 | # node.updated(nil, process_all(node)) 346 | # end 347 | # 348 | # def on_store(node) 349 | # expr, variable_name = *node 350 | # 351 | # # Note that variable_name is not a Node and thus isn't 352 | # # passed to #process. 353 | # node.updated(nil, [ 354 | # process(expr), 355 | # variable_name 356 | # ]) 357 | # end 358 | # 359 | # # (load) is effectively a terminal node, and so it does 360 | # # not need an explicit handler, as the following is the 361 | # # default behavior. Essentially, for any nodes that don't 362 | # # have a defined handler, the node remains unchanged. 363 | # def on_load(node) 364 | # nil 365 | # end 366 | # 367 | # def on_each(node) 368 | # node.updated(nil, process_all(node)) 369 | # end 370 | # end 371 | # 372 | # Let's test our ArithmeticsProcessor: 373 | # 374 | # include AST::Sexp 375 | # expr = s(:add, s(:integer, 2), s(:integer, 2)) 376 | # 377 | # p ArithmeticsProcessor.new.process(expr) == expr # => true 378 | # 379 | # As expected, it does not change anything at all. This isn't 380 | # actually very useful, so let's now define a Calculator, which 381 | # will compute the expression values: 382 | # 383 | # # This Processor folds nonterminal nodes and returns an 384 | # # (integer) terminal node. 385 | # class ArithmeticsCalculator < ArithmeticsProcessor 386 | # def compute_op(node) 387 | # # First, node children are processed and then unpacked 388 | # # to local variables. 389 | # nodes = process_all(node) 390 | # 391 | # if nodes.all? { |node| node.type == :integer } 392 | # # If each of those nodes represents a literal, we can 393 | # # fold this node! 394 | # values = nodes.map { |node| node.children.first } 395 | # AST::Node.new(:integer, [ 396 | # yield(values) 397 | # ]) 398 | # else 399 | # # Otherwise, we can just leave the current node in the 400 | # # tree and only update it with processed children 401 | # # nodes, which can be partially folded. 402 | # node.updated(nil, nodes) 403 | # end 404 | # end 405 | # 406 | # def on_add(node) 407 | # compute_op(node) { |left, right| left + right } 408 | # end 409 | # 410 | # def on_multiply(node) 411 | # compute_op(node) { |left, right| left * right } 412 | # end 413 | # end 414 | # 415 | # Let's check: 416 | # 417 | # p ArithmeticsCalculator.new.process(expr) # => (integer 4) 418 | # 419 | # Excellent, the calculator works! Now, a careful reader could 420 | # notice that the ArithmeticsCalculator does not know how to 421 | # divide numbers. What if we pass an expression with division to 422 | # it? 423 | # 424 | # expr_with_division = \ 425 | # s(:add, 426 | # s(:integer, 1), 427 | # s(:divide, 428 | # s(:add, s(:integer, 8), s(:integer, 4)), 429 | # s(:integer, 3))) # 1 + (8 + 4) / 3 430 | # 431 | # folded_expr_with_division = ArithmeticsCalculator.new.process(expr_with_division) 432 | # p folded_expr_with_division 433 | # # => (add 434 | # # (integer 1) 435 | # # (divide 436 | # # (integer 12) 437 | # # (integer 3))) 438 | # 439 | # As you can see, the expression was folded _partially_: the inner 440 | # `(add)` node which could be computed was folded to 441 | # `(integer 12)`, the `(divide)` node is left as-is because there 442 | # is no computing handler for it, and the root `(add)` node was 443 | # also left as it is because some of its children were not 444 | # literals. 445 | # 446 | # Note that this partial folding is only possible because the 447 | # _data_ format, i.e. the format in which the computed values of 448 | # the nodes are represented, is the same as the AST itself. 449 | # 450 | # Let's extend our ArithmeticsCalculator class further. 451 | # 452 | # class ArithmeticsCalculator 453 | # def on_divide(node) 454 | # compute_op(node) { |left, right| left / right } 455 | # end 456 | # 457 | # def on_negate(node) 458 | # # Note how #compute_op works regardless of the operator 459 | # # arity. 460 | # compute_op(node) { |value| -value } 461 | # end 462 | # end 463 | # 464 | # Now, let's apply our renewed ArithmeticsCalculator to a partial 465 | # result of previous evaluation: 466 | # 467 | # p ArithmeticsCalculator.new.process(expr_with_division) # => (integer 5) 468 | # 469 | # Five! Excellent. This is also pretty much how CRuby 1.8 executed 470 | # its programs. 471 | # 472 | # Now, let's do some automated bug searching. Division by zero is 473 | # an error, right? So if we could detect that someone has divided 474 | # by zero before the program is even run, that could save some 475 | # debugging time. 476 | # 477 | # class DivisionByZeroVerifier < ArithmeticsProcessor 478 | # class VerificationFailure < Exception; end 479 | # 480 | # def on_divide(node) 481 | # # You need to process the children to handle nested divisions 482 | # # such as: 483 | # # (divide 484 | # # (integer 1) 485 | # # (divide (integer 1) (integer 0)) 486 | # left, right = process_all(node) 487 | # 488 | # if right.type == :integer && 489 | # right.children.first == 0 490 | # raise VerificationFailure, "Ouch! This code divides by zero." 491 | # end 492 | # end 493 | # 494 | # def divides_by_zero?(ast) 495 | # process(ast) 496 | # false 497 | # rescue VerificationFailure 498 | # true 499 | # end 500 | # end 501 | # 502 | # nice_expr = \ 503 | # s(:divide, 504 | # s(:add, s(:integer, 10), s(:integer, 2)), 505 | # s(:integer, 4)) 506 | # 507 | # p DivisionByZeroVerifier.new.divides_by_zero?(nice_expr) 508 | # # => false. Good. 509 | # 510 | # bad_expr = \ 511 | # s(:add, s(:integer, 10), 512 | # s(:divide, s(:integer, 1), s(:integer, 0))) 513 | # 514 | # p DivisionByZeroVerifier.new.divides_by_zero?(bad_expr) 515 | # # => true. WHOOPS. DO NOT RUN THIS. 516 | # 517 | # Of course, this won't detect more complex cases... unless you 518 | # use some partial evaluation before! The possibilites are 519 | # endless. Have fun. 520 | # 521 | # source://ast//lib/ast/processor/mixin.rb#240 522 | module AST::Processor::Mixin 523 | # Default handler. Does nothing. 524 | # 525 | # @param node [AST::Node] 526 | # @return [AST::Node, nil] 527 | # 528 | # source://ast//lib/ast/processor/mixin.rb#284 529 | def handler_missing(node); end 530 | 531 | # Dispatches `node`. If a node has type `:foo`, then a handler 532 | # named `on_foo` is invoked with one argument, the `node`; if 533 | # there isn't such a handler, {#handler_missing} is invoked 534 | # with the same argument. 535 | # 536 | # If the handler returns `nil`, `node` is returned; otherwise, 537 | # the return value of the handler is passed along. 538 | # 539 | # @param node [AST::Node, nil] 540 | # @return [AST::Node, nil] 541 | # 542 | # source://ast//lib/ast/processor/mixin.rb#251 543 | def process(node); end 544 | 545 | # {#process}es each node from `nodes` and returns an array of 546 | # results. 547 | # 548 | # @param nodes [Array] 549 | # @return [Array] 550 | # 551 | # source://ast//lib/ast/processor/mixin.rb#274 552 | def process_all(nodes); end 553 | end 554 | 555 | # This simple module is very useful in the cases where one needs 556 | # to define deeply nested ASTs from Ruby code, for example, in 557 | # tests. It should be used like this: 558 | # 559 | # describe YourLanguage::AST do 560 | # include Sexp 561 | # 562 | # it "should correctly parse expressions" do 563 | # YourLanguage.parse("1 + 2 * 3").should == 564 | # s(:add, 565 | # s(:integer, 1), 566 | # s(:multiply, 567 | # s(:integer, 2), 568 | # s(:integer, 3))) 569 | # end 570 | # end 571 | # 572 | # This way the amount of boilerplate code is greatly reduced. 573 | # 574 | # source://ast//lib/ast/sexp.rb#20 575 | module AST::Sexp 576 | # Creates a {Node} with type `type` and children `children`. 577 | # Note that the resulting node is of the type AST::Node and not a 578 | # subclass. 579 | # This would not pose a problem with comparisons, as {Node#==} 580 | # ignores metadata. 581 | # 582 | # source://ast//lib/ast/sexp.rb#26 583 | def s(type, *children); end 584 | end 585 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/zeitwerk@2.6.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `zeitwerk` gem. 5 | # Please instead update this file by running `bin/tapioca gem zeitwerk`. 6 | 7 | # source://zeitwerk//lib/zeitwerk/kernel.rb#3 8 | module Kernel 9 | private 10 | 11 | # source://zeitwerk//lib/zeitwerk/kernel.rb#24 12 | def require(path); end 13 | 14 | class << self 15 | # source://zeitwerk//lib/zeitwerk/kernel.rb#24 16 | def require(path); end 17 | end 18 | end 19 | 20 | # source://zeitwerk//lib/zeitwerk.rb#3 21 | module Zeitwerk 22 | class << self 23 | # This is a dangerous method. 24 | # 25 | # source://zeitwerk//lib/zeitwerk.rb#19 26 | def with_loader; end 27 | end 28 | end 29 | 30 | # source://zeitwerk//lib/zeitwerk/error.rb#4 31 | class Zeitwerk::Error < ::StandardError; end 32 | 33 | # Centralizes the logic for the trace point used to detect the creation of 34 | # explicit namespaces, needed to descend into matching subdirectories right 35 | # after the constant has been defined. 36 | # 37 | # The implementation assumes an explicit namespace is managed by one loader. 38 | # Loaders that reopen namespaces owned by other projects are responsible for 39 | # loading their constant before setup. This is documented. 40 | # 41 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#11 42 | module Zeitwerk::ExplicitNamespace 43 | extend ::Zeitwerk::RealModName 44 | 45 | class << self 46 | # Maps constant paths that correspond to explicit namespaces according to 47 | # the file system, to the loader responsible for them. 48 | # 49 | # @private 50 | # 51 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#20 52 | def cpaths; end 53 | 54 | # @private 55 | # 56 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#24 57 | def mutex; end 58 | 59 | # Asserts `cpath` corresponds to an explicit namespace for which `loader` 60 | # is responsible. 61 | # 62 | # @private 63 | # 64 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#35 65 | def register(cpath, loader); end 66 | 67 | # @private 68 | # 69 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#28 70 | def tracer; end 71 | 72 | # @private 73 | # 74 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#46 75 | def unregister_loader(loader); end 76 | 77 | private 78 | 79 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#54 80 | def disable_tracer_if_unneeded; end 81 | 82 | # source://zeitwerk//lib/zeitwerk/explicit_namespace.rb#61 83 | def tracepoint_class_callback(event); end 84 | end 85 | end 86 | 87 | # source://zeitwerk//lib/zeitwerk/gem_inflector.rb#5 88 | class Zeitwerk::GemInflector < ::Zeitwerk::Inflector 89 | # @return [GemInflector] a new instance of GemInflector 90 | # 91 | # source://zeitwerk//lib/zeitwerk/gem_inflector.rb#6 92 | def initialize(root_file); end 93 | 94 | # source://zeitwerk//lib/zeitwerk/gem_inflector.rb#13 95 | def camelize(basename, abspath); end 96 | end 97 | 98 | # @private 99 | # 100 | # source://zeitwerk//lib/zeitwerk/gem_loader.rb#7 101 | class Zeitwerk::GemLoader < ::Zeitwerk::Loader 102 | # @return [GemLoader] a new instance of GemLoader 103 | # 104 | # source://zeitwerk//lib/zeitwerk/gem_loader.rb#17 105 | def initialize(root_file, warn_on_extra_files:); end 106 | 107 | # source://zeitwerk//lib/zeitwerk/gem_loader.rb#30 108 | def setup; end 109 | 110 | private 111 | 112 | # source://zeitwerk//lib/zeitwerk/gem_loader.rb#38 113 | def warn_on_extra_files; end 114 | 115 | class << self 116 | # @private 117 | # 118 | # source://zeitwerk//lib/zeitwerk/gem_loader.rb#12 119 | def _new(root_file, warn_on_extra_files:); end 120 | end 121 | end 122 | 123 | # source://zeitwerk//lib/zeitwerk/inflector.rb#4 124 | class Zeitwerk::Inflector 125 | # Very basic snake case -> camel case conversion. 126 | # 127 | # inflector = Zeitwerk::Inflector.new 128 | # inflector.camelize("post", ...) # => "Post" 129 | # inflector.camelize("users_controller", ...) # => "UsersController" 130 | # inflector.camelize("api", ...) # => "Api" 131 | # 132 | # Takes into account hard-coded mappings configured with `inflect`. 133 | # 134 | # source://zeitwerk//lib/zeitwerk/inflector.rb#15 135 | def camelize(basename, _abspath); end 136 | 137 | # Configures hard-coded inflections: 138 | # 139 | # inflector = Zeitwerk::Inflector.new 140 | # inflector.inflect( 141 | # "html_parser" => "HTMLParser", 142 | # "mysql_adapter" => "MySQLAdapter" 143 | # ) 144 | # 145 | # inflector.camelize("html_parser", abspath) # => "HTMLParser" 146 | # inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter" 147 | # inflector.camelize("users_controller", abspath) # => "UsersController" 148 | # 149 | # source://zeitwerk//lib/zeitwerk/inflector.rb#32 150 | def inflect(inflections); end 151 | 152 | private 153 | 154 | # Hard-coded basename to constant name user maps that override the default 155 | # inflection logic. 156 | # 157 | # source://zeitwerk//lib/zeitwerk/inflector.rb#42 158 | def overrides; end 159 | end 160 | 161 | # source://zeitwerk//lib/zeitwerk/loader.rb#6 162 | class Zeitwerk::Loader 163 | include ::Zeitwerk::RealModName 164 | include ::Zeitwerk::Loader::Callbacks 165 | include ::Zeitwerk::Loader::Helpers 166 | include ::Zeitwerk::Loader::Config 167 | 168 | # @return [Loader] a new instance of Loader 169 | # 170 | # source://zeitwerk//lib/zeitwerk/loader.rb#83 171 | def initialize; end 172 | 173 | # We keep track of autoloaded directories to remove them from the registry 174 | # at the end of eager loading. 175 | # 176 | # Files are removed as they are autoloaded, but directories need to wait due 177 | # to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded). 178 | # 179 | # @private 180 | # 181 | # source://zeitwerk//lib/zeitwerk/loader.rb#39 182 | def autoloaded_dirs; end 183 | 184 | # Maps absolute paths for which an autoload has been set ---and not 185 | # executed--- to their corresponding parent class or module and constant 186 | # name. 187 | # 188 | # "/Users/fxn/blog/app/models/user.rb" => [Object, :User], 189 | # "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing] 190 | # ... 191 | # 192 | # @private 193 | # 194 | # source://zeitwerk//lib/zeitwerk/loader.rb#29 195 | def autoloads; end 196 | 197 | # Eager loads all files in the root directories, recursively. Files do not 198 | # need to be in `$LOAD_PATH`, absolute file names are used. Ignored files 199 | # are not eager loaded. You can opt-out specifically in specific files and 200 | # directories with `do_not_eager_load`, and that can be overridden passing 201 | # `force: true`. 202 | # 203 | # source://zeitwerk//lib/zeitwerk/loader.rb#218 204 | def eager_load(force: T.unsafe(nil)); end 205 | 206 | # Maps constant paths of namespaces to arrays of corresponding directories. 207 | # 208 | # For example, given this mapping: 209 | # 210 | # "Admin" => [ 211 | # "/Users/fxn/blog/app/controllers/admin", 212 | # "/Users/fxn/blog/app/models/admin", 213 | # ... 214 | # ] 215 | # 216 | # when `Admin` gets defined we know that it plays the role of a namespace and 217 | # that its children are spread over those directories. We'll visit them to set 218 | # up the corresponding autoloads. 219 | # 220 | # @private 221 | # 222 | # source://zeitwerk//lib/zeitwerk/loader.rb#73 223 | def lazy_subdirs; end 224 | 225 | # @private 226 | # 227 | # source://zeitwerk//lib/zeitwerk/loader.rb#77 228 | def mutex; end 229 | 230 | # @private 231 | # 232 | # source://zeitwerk//lib/zeitwerk/loader.rb#81 233 | def mutex2; end 234 | 235 | # Unloads all loaded code, and calls setup again so that the loader is able 236 | # to pick any changes in the file system. 237 | # 238 | # This method is not thread-safe, please see how this can be achieved by 239 | # client code in the README of the project. 240 | # 241 | # @raise [Zeitwerk::Error] 242 | # 243 | # source://zeitwerk//lib/zeitwerk/loader.rb#202 244 | def reload; end 245 | 246 | # Sets autoloads in the root namespace. 247 | # 248 | # source://zeitwerk//lib/zeitwerk/loader.rb#101 249 | def setup; end 250 | 251 | # Stores metadata needed for unloading. Its entries look like this: 252 | # 253 | # "Admin::Role" => [".../admin/role.rb", [Admin, :Role]] 254 | # 255 | # The cpath as key helps implementing unloadable_cpath? The file name is 256 | # stored in order to be able to delete it from $LOADED_FEATURES, and the 257 | # pair [Module, Symbol] is used to remove_const the constant from the class 258 | # or module object. 259 | # 260 | # If reloading is enabled, this hash is filled as constants are autoloaded 261 | # or eager loaded. Otherwise, the collection remains empty. 262 | # 263 | # @private 264 | # 265 | # source://zeitwerk//lib/zeitwerk/loader.rb#55 266 | def to_unload; end 267 | 268 | # Removes loaded constants and configured autoloads. 269 | # 270 | # The objects the constants stored are no longer reachable through them. In 271 | # addition, since said objects are normally not referenced from anywhere 272 | # else, they are eligible for garbage collection, which would effectively 273 | # unload them. 274 | # 275 | # This method is public but undocumented. Main interface is `reload`, which 276 | # means `unload` + `setup`. This one is avaiable to be used together with 277 | # `unregister`, which is undocumented too. 278 | # 279 | # source://zeitwerk//lib/zeitwerk/loader.rb#127 280 | def unload; end 281 | 282 | # Says if the given constant path would be unloaded on reload. This 283 | # predicate returns `false` if reloading is disabled. 284 | # 285 | # @return [Boolean] 286 | # 287 | # source://zeitwerk//lib/zeitwerk/loader.rb#267 288 | def unloadable_cpath?(cpath); end 289 | 290 | # Returns an array with the constant paths that would be unloaded on reload. 291 | # This predicate returns an empty array if reloading is disabled. 292 | # 293 | # source://zeitwerk//lib/zeitwerk/loader.rb#275 294 | def unloadable_cpaths; end 295 | 296 | # This is a dangerous method. 297 | # 298 | # source://zeitwerk//lib/zeitwerk/loader.rb#283 299 | def unregister; end 300 | 301 | private 302 | 303 | # source://zeitwerk//lib/zeitwerk/loader.rb#397 304 | def autoload_file(parent, cname, file); end 305 | 306 | # @return [Boolean] 307 | # 308 | # source://zeitwerk//lib/zeitwerk/loader.rb#453 309 | def autoload_path_set_by_me_for?(parent, cname); end 310 | 311 | # source://zeitwerk//lib/zeitwerk/loader.rb#376 312 | def autoload_subdir(parent, cname, subdir); end 313 | 314 | # `dir` is the directory that would have autovivified a namespace. `file` is 315 | # the file where we've found the namespace is explicitly defined. 316 | # 317 | # source://zeitwerk//lib/zeitwerk/loader.rb#421 318 | def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:); end 319 | 320 | # source://zeitwerk//lib/zeitwerk/loader.rb#467 321 | def raise_if_conflicting_directory(dir); end 322 | 323 | # source://zeitwerk//lib/zeitwerk/loader.rb#462 324 | def register_explicit_namespace(cpath); end 325 | 326 | # source://zeitwerk//lib/zeitwerk/loader.rb#491 327 | def run_on_unload_callbacks(cpath, value, abspath); end 328 | 329 | # source://zeitwerk//lib/zeitwerk/loader.rb#432 330 | def set_autoload(parent, cname, abspath); end 331 | 332 | # source://zeitwerk//lib/zeitwerk/loader.rb#333 333 | def set_autoloads_in_dir(dir, parent); end 334 | 335 | # source://zeitwerk//lib/zeitwerk/loader.rb#498 336 | def unload_autoload(parent, cname); end 337 | 338 | # source://zeitwerk//lib/zeitwerk/loader.rb#504 339 | def unload_cref(parent, cname); end 340 | 341 | class << self 342 | # Returns an array with the absolute paths of the root directories of all 343 | # registered loaders. This is a read-only collection. 344 | # 345 | # source://zeitwerk//lib/zeitwerk/loader.rb#325 346 | def all_dirs; end 347 | 348 | # Returns the value of attribute default_logger. 349 | # 350 | # source://zeitwerk//lib/zeitwerk/loader.rb#292 351 | def default_logger; end 352 | 353 | # Sets the attribute default_logger 354 | # 355 | # @param value the value to set the attribute default_logger to. 356 | # 357 | # source://zeitwerk//lib/zeitwerk/loader.rb#292 358 | def default_logger=(_arg0); end 359 | 360 | # Broadcasts `eager_load` to all loaders. 361 | # 362 | # source://zeitwerk//lib/zeitwerk/loader.rb#317 363 | def eager_load_all; end 364 | 365 | # This is a shortcut for 366 | # 367 | # require "zeitwerk" 368 | # loader = Zeitwerk::Loader.new 369 | # loader.tag = File.basename(__FILE__, ".rb") 370 | # loader.inflector = Zeitwerk::GemInflector.new(__FILE__) 371 | # loader.push_dir(__dir__) 372 | # 373 | # except that this method returns the same object in subsequent calls from 374 | # the same file, in the unlikely case the gem wants to be able to reload. 375 | # 376 | # This method returns a subclass of Zeitwerk::Loader, but the exact type 377 | # is private, client code can only rely on the interface. 378 | # 379 | # source://zeitwerk//lib/zeitwerk/loader.rb#309 380 | def for_gem(warn_on_extra_files: T.unsafe(nil)); end 381 | end 382 | end 383 | 384 | # source://zeitwerk//lib/zeitwerk/loader/callbacks.rb#3 385 | module Zeitwerk::Loader::Callbacks 386 | include ::Zeitwerk::RealModName 387 | 388 | # Invoked from our decorated Kernel#require when a managed directory is 389 | # autoloaded. 390 | # 391 | # @private 392 | # 393 | # source://zeitwerk//lib/zeitwerk/loader/callbacks.rb#34 394 | def on_dir_autoloaded(dir); end 395 | 396 | # Invoked from our decorated Kernel#require when a managed file is autoloaded. 397 | # 398 | # @private 399 | # 400 | # source://zeitwerk//lib/zeitwerk/loader/callbacks.rb#10 401 | def on_file_autoloaded(file); end 402 | 403 | # Invoked when a class or module is created or reopened, either from the 404 | # tracer or from module autovivification. If the namespace has matching 405 | # subdirectories, we descend into them now. 406 | # 407 | # @private 408 | # 409 | # source://zeitwerk//lib/zeitwerk/loader/callbacks.rb#73 410 | def on_namespace_loaded(namespace); end 411 | 412 | private 413 | 414 | # source://zeitwerk//lib/zeitwerk/loader/callbacks.rb#84 415 | def run_on_load_callbacks(cpath, value, abspath); end 416 | end 417 | 418 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#6 419 | module Zeitwerk::Loader::Config 420 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#82 421 | def initialize; end 422 | 423 | # Configure directories or glob patterns to be collapsed. 424 | # 425 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#191 426 | def collapse(*glob_patterns); end 427 | 428 | # The actual collection of absolute directory names at the time the collapse 429 | # glob patterns were expanded. Computed on setup, and recomputed on reload. 430 | # 431 | # @private 432 | # 433 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#51 434 | def collapse_dirs; end 435 | 436 | # Absolute paths of directories or glob patterns to be collapsed. 437 | # 438 | # @private 439 | # 440 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#44 441 | def collapse_glob_patterns; end 442 | 443 | # Absolute paths of the root directories. This is a read-only collection, 444 | # please push here via `push_dir`. 445 | # 446 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#143 447 | def dirs; end 448 | 449 | # Let eager load ignore the given files or directories. The constants defined 450 | # in those files are still autoloadable. 451 | # 452 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#173 453 | def do_not_eager_load(*paths); end 454 | 455 | # Absolute paths of files or directories not to be eager loaded. 456 | # 457 | # @private 458 | # 459 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#57 460 | def eager_load_exclusions; end 461 | 462 | # You need to call this method before setup in order to be able to reload. 463 | # There is no way to undo this, either you want to reload or you don't. 464 | # 465 | # @raise [Zeitwerk::Error] 466 | # 467 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#152 468 | def enable_reloading; end 469 | 470 | # Configure files, directories, or glob patterns to be totally ignored. 471 | # 472 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#180 473 | def ignore(*glob_patterns); end 474 | 475 | # Absolute paths of files, directories, or glob patterns to be totally 476 | # ignored. 477 | # 478 | # @private 479 | # 480 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#30 481 | def ignored_glob_patterns; end 482 | 483 | # The actual collection of absolute file and directory names at the time the 484 | # ignored glob patterns were expanded. Computed on setup, and recomputed on 485 | # reload. 486 | # 487 | # @private 488 | # 489 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#38 490 | def ignored_paths; end 491 | 492 | # @private 493 | # @return [Boolean] 494 | # 495 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#269 496 | def ignores?(abspath); end 497 | 498 | # Returns the value of attribute inflector. 499 | # 500 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#23 501 | def inflector; end 502 | 503 | # Sets the attribute inflector 504 | # 505 | # @param value the value to set the attribute inflector to. 506 | # 507 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#23 508 | def inflector=(_arg0); end 509 | 510 | # Logs to `$stdout`, handy shortcut for debugging. 511 | # 512 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#263 513 | def log!; end 514 | 515 | # Returns the value of attribute logger. 516 | # 517 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#80 518 | def logger; end 519 | 520 | # Sets the attribute logger 521 | # 522 | # @param value the value to set the attribute logger to. 523 | # 524 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#80 525 | def logger=(_arg0); end 526 | 527 | # Configure a block to be invoked once a certain constant path is loaded. 528 | # Supports multiple callbacks, and if there are many, they are executed in 529 | # the order in which they were defined. 530 | # 531 | # loader.on_load("SomeApiClient") do |klass, _abspath| 532 | # klass.endpoint = "https://api.dev" 533 | # end 534 | # 535 | # Can also be configured for any constant loaded: 536 | # 537 | # loader.on_load do |cpath, value, abspath| 538 | # # ... 539 | # end 540 | # 541 | # @raise [TypeError] 542 | # 543 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#227 544 | def on_load(cpath = T.unsafe(nil), &block); end 545 | 546 | # User-oriented callbacks to be fired when a constant is loaded. 547 | # 548 | # @private 549 | # 550 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#70 551 | def on_load_callbacks; end 552 | 553 | # Configure a block to be called after setup and on each reload. 554 | # If setup was already done, the block runs immediately. 555 | # 556 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#203 557 | def on_setup(&block); end 558 | 559 | # User-oriented callbacks to be fired on setup and on reload. 560 | # 561 | # @private 562 | # 563 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#63 564 | def on_setup_callbacks; end 565 | 566 | # Configure a block to be invoked right before a certain constant is removed. 567 | # Supports multiple callbacks, and if there are many, they are executed in the 568 | # order in which they were defined. 569 | # 570 | # loader.on_unload("Country") do |klass, _abspath| 571 | # klass.clear_cache 572 | # end 573 | # 574 | # Can also be configured for any removed constant: 575 | # 576 | # loader.on_unload do |cpath, value, abspath| 577 | # # ... 578 | # end 579 | # 580 | # @raise [TypeError] 581 | # 582 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#252 583 | def on_unload(cpath = T.unsafe(nil), &block); end 584 | 585 | # User-oriented callbacks to be fired before constants are removed. 586 | # 587 | # @private 588 | # 589 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#77 590 | def on_unload_callbacks; end 591 | 592 | # Pushes `path` to the list of root directories. 593 | # 594 | # Raises `Zeitwerk::Error` if `path` does not exist, or if another loader in 595 | # the same process already manages that directory or one of its ascendants or 596 | # descendants. 597 | # 598 | # @raise [Zeitwerk::Error] 599 | # 600 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#107 601 | def push_dir(path, namespace: T.unsafe(nil)); end 602 | 603 | # @return [Boolean] 604 | # 605 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#165 606 | def reloading_enabled?; end 607 | 608 | # Absolute paths of the root directories. Stored in a hash to preserve 609 | # order, easily handle duplicates, and also be able to have a fast lookup, 610 | # needed for detecting nested paths. 611 | # 612 | # "/Users/fxn/blog/app/assets" => true, 613 | # "/Users/fxn/blog/app/channels" => true, 614 | # ... 615 | # 616 | # This is a private collection maintained by the loader. The public 617 | # interface for it is `push_dir` and `dirs`. 618 | # 619 | # @private 620 | # 621 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#20 622 | def root_dirs; end 623 | 624 | # Returns the loader's tag. 625 | # 626 | # Implemented as a method instead of via attr_reader for symmetry with the 627 | # writer below. 628 | # 629 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#128 630 | def tag; end 631 | 632 | # Sets a tag for the loader, useful for logging. 633 | # 634 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#135 635 | def tag=(tag); end 636 | 637 | private 638 | 639 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#278 640 | def actual_root_dirs; end 641 | 642 | # @return [Boolean] 643 | # 644 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#295 645 | def collapse?(dir); end 646 | 647 | # @return [Boolean] 648 | # 649 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#290 650 | def excluded_from_eager_load?(abspath); end 651 | 652 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#305 653 | def expand_glob_patterns(glob_patterns); end 654 | 655 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#300 656 | def expand_paths(paths); end 657 | 658 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#317 659 | def recompute_collapse_dirs; end 660 | 661 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#312 662 | def recompute_ignored_paths; end 663 | 664 | # @return [Boolean] 665 | # 666 | # source://zeitwerk//lib/zeitwerk/loader/config.rb#285 667 | def root_dir?(dir); end 668 | end 669 | 670 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#3 671 | module Zeitwerk::Loader::Helpers 672 | private 673 | 674 | # @return [Boolean] 675 | # 676 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#120 677 | def cdef?(parent, cname); end 678 | 679 | # @raise [NameError] 680 | # 681 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#126 682 | def cget(parent, cname); end 683 | 684 | # Symbol#name was introduced in Ruby 3.0. It returns always the same 685 | # frozen object, so we may save a few string allocations. 686 | # 687 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#110 688 | def cpath(parent, cname); end 689 | 690 | # @return [Boolean] 691 | # 692 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#68 693 | def dir?(path); end 694 | 695 | # @return [Boolean] 696 | # 697 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#46 698 | def has_at_least_one_ruby_file?(dir); end 699 | 700 | # @return [Boolean] 701 | # 702 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#73 703 | def hidden?(basename); end 704 | 705 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#9 706 | def log(message); end 707 | 708 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#17 709 | def ls(dir); end 710 | 711 | # @return [Boolean] 712 | # 713 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#63 714 | def ruby?(path); end 715 | 716 | # source://zeitwerk//lib/zeitwerk/loader/helpers.rb#101 717 | def strict_autoload_path(parent, cname); end 718 | end 719 | 720 | # source://zeitwerk//lib/zeitwerk/loader.rb#16 721 | Zeitwerk::Loader::MUTEX = T.let(T.unsafe(nil), Thread::Mutex) 722 | 723 | # source://zeitwerk//lib/zeitwerk/error.rb#13 724 | class Zeitwerk::NameError < ::NameError; end 725 | 726 | # source://zeitwerk//lib/zeitwerk/real_mod_name.rb#3 727 | module Zeitwerk::RealModName 728 | # source://zeitwerk//lib/zeitwerk/real_mod_name.rb#14 729 | def real_mod_name(mod); end 730 | end 731 | 732 | # source://zeitwerk//lib/zeitwerk/real_mod_name.rb#4 733 | Zeitwerk::RealModName::UNBOUND_METHOD_MODULE_NAME = T.let(T.unsafe(nil), UnboundMethod) 734 | 735 | # source://zeitwerk//lib/zeitwerk/registry.rb#4 736 | module Zeitwerk::Registry 737 | class << self 738 | # Maps absolute paths to the loaders responsible for them. 739 | # 740 | # This information is used by our decorated `Kernel#require` to be able to 741 | # invoke callbacks and autovivify modules. 742 | # 743 | # @private 744 | # 745 | # source://zeitwerk//lib/zeitwerk/registry.rb#26 746 | def autoloads; end 747 | 748 | # Registers gem loaders to let `for_gem` be idempotent in case of reload. 749 | # 750 | # @private 751 | # 752 | # source://zeitwerk//lib/zeitwerk/registry.rb#17 753 | def gem_loaders_by_root_file; end 754 | 755 | # @private 756 | # @return [Boolean] 757 | # 758 | # source://zeitwerk//lib/zeitwerk/registry.rb#113 759 | def inception?(cpath); end 760 | 761 | # This hash table addresses an edge case in which an autoload is ignored. 762 | # 763 | # For example, let's suppose we want to autoload in a gem like this: 764 | # 765 | # # lib/my_gem.rb 766 | # loader = Zeitwerk::Loader.new 767 | # loader.push_dir(__dir__) 768 | # loader.setup 769 | # 770 | # module MyGem 771 | # end 772 | # 773 | # if you require "my_gem", as Bundler would do, this happens while setting 774 | # up autoloads: 775 | # 776 | # 1. Object.autoload?(:MyGem) returns `nil` because the autoload for 777 | # the constant is issued by Zeitwerk while the same file is being 778 | # required. 779 | # 2. The constant `MyGem` is undefined while setup runs. 780 | # 781 | # Therefore, a directory `lib/my_gem` would autovivify a module according to 782 | # the existing information. But that would be wrong. 783 | # 784 | # To overcome this fundamental limitation, we keep track of the constant 785 | # paths that are in this situation ---in the example above, "MyGem"--- and 786 | # take this collection into account for the autovivification logic. 787 | # 788 | # Note that you cannot generally address this by moving the setup code 789 | # below the constant definition, because we want libraries to be able to 790 | # use managed constants in the module body: 791 | # 792 | # module MyGem 793 | # include MyConcern 794 | # end 795 | # 796 | # @private 797 | # 798 | # source://zeitwerk//lib/zeitwerk/registry.rb#65 799 | def inceptions; end 800 | 801 | # @private 802 | # 803 | # source://zeitwerk//lib/zeitwerk/registry.rb#121 804 | def loader_for(path); end 805 | 806 | # This method returns always a loader, the same instance for the same root 807 | # file. That is how Zeitwerk::Loader.for_gem is idempotent. 808 | # 809 | # @private 810 | # 811 | # source://zeitwerk//lib/zeitwerk/registry.rb#89 812 | def loader_for_gem(root_file, warn_on_extra_files:); end 813 | 814 | # Keeps track of all loaders. Useful to broadcast messages and to prevent 815 | # them from being garbage collected. 816 | # 817 | # @private 818 | # 819 | # source://zeitwerk//lib/zeitwerk/registry.rb#11 820 | def loaders; end 821 | 822 | # @private 823 | # 824 | # source://zeitwerk//lib/zeitwerk/registry.rb#127 825 | def on_unload(loader); end 826 | 827 | # @private 828 | # 829 | # source://zeitwerk//lib/zeitwerk/registry.rb#95 830 | def register_autoload(loader, abspath); end 831 | 832 | # @private 833 | # 834 | # source://zeitwerk//lib/zeitwerk/registry.rb#107 835 | def register_inception(cpath, abspath, loader); end 836 | 837 | # Registers a loader. 838 | # 839 | # @private 840 | # 841 | # source://zeitwerk//lib/zeitwerk/registry.rb#71 842 | def register_loader(loader); end 843 | 844 | # @private 845 | # 846 | # source://zeitwerk//lib/zeitwerk/registry.rb#101 847 | def unregister_autoload(abspath); end 848 | 849 | # @private 850 | # 851 | # source://zeitwerk//lib/zeitwerk/registry.rb#77 852 | def unregister_loader(loader); end 853 | end 854 | end 855 | 856 | # source://zeitwerk//lib/zeitwerk/error.rb#7 857 | class Zeitwerk::ReloadingDisabledError < ::Zeitwerk::Error 858 | # @return [ReloadingDisabledError] a new instance of ReloadingDisabledError 859 | # 860 | # source://zeitwerk//lib/zeitwerk/error.rb#8 861 | def initialize; end 862 | end 863 | 864 | # source://zeitwerk//lib/zeitwerk/version.rb#4 865 | Zeitwerk::VERSION = T.let(T.unsafe(nil), String) 866 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/diff-lcs@1.5.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `diff-lcs` gem. 5 | # Please instead update this file by running `bin/tapioca gem diff-lcs`. 6 | 7 | # source://diff-lcs//lib/diff/lcs.rb#3 8 | module Diff; end 9 | 10 | # source://diff-lcs//lib/diff/lcs.rb#51 11 | module Diff::LCS 12 | # Returns the difference set between +self+ and +other+. See Diff::LCS#diff. 13 | # 14 | # source://diff-lcs//lib/diff/lcs.rb#75 15 | def diff(other, callbacks = T.unsafe(nil), &block); end 16 | 17 | # Returns an Array containing the longest common subsequence(s) between 18 | # +self+ and +other+. See Diff::LCS#lcs. 19 | # 20 | # lcs = seq1.lcs(seq2) 21 | # 22 | # A note when using objects: Diff::LCS only works properly when each object 23 | # can be used as a key in a Hash, which typically means that the objects must 24 | # implement Object#eql? in a way that two identical values compare 25 | # identically for key purposes. That is: 26 | # 27 | # O.new('a').eql?(O.new('a')) == true 28 | # 29 | # source://diff-lcs//lib/diff/lcs.rb#70 30 | def lcs(other, &block); end 31 | 32 | # Attempts to patch +self+ with the provided +patchset+. A new sequence based 33 | # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Attempts 34 | # to autodiscover the direction of the patch. 35 | # 36 | # source://diff-lcs//lib/diff/lcs.rb#101 37 | def patch(patchset); end 38 | 39 | # Attempts to patch +self+ with the provided +patchset+. A new sequence based 40 | # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Does no 41 | # patch direction autodiscovery. 42 | # 43 | # source://diff-lcs//lib/diff/lcs.rb#109 44 | def patch!(patchset); end 45 | 46 | # Attempts to patch +self+ with the provided +patchset+, using #patch!. If 47 | # the sequence this is used on supports #replace, the value of +self+ will be 48 | # replaced. See Diff::LCS#patch. Does no patch direction autodiscovery. 49 | # 50 | # source://diff-lcs//lib/diff/lcs.rb#123 51 | def patch_me(patchset); end 52 | 53 | # Returns the balanced ("side-by-side") difference set between +self+ and 54 | # +other+. See Diff::LCS#sdiff. 55 | # 56 | # source://diff-lcs//lib/diff/lcs.rb#81 57 | def sdiff(other, callbacks = T.unsafe(nil), &block); end 58 | 59 | # Traverses the discovered longest common subsequences between +self+ and 60 | # +other+ using the alternate, balanced algorithm. See 61 | # Diff::LCS#traverse_balanced. 62 | # 63 | # source://diff-lcs//lib/diff/lcs.rb#94 64 | def traverse_balanced(other, callbacks = T.unsafe(nil), &block); end 65 | 66 | # Traverses the discovered longest common subsequences between +self+ and 67 | # +other+. See Diff::LCS#traverse_sequences. 68 | # 69 | # source://diff-lcs//lib/diff/lcs.rb#87 70 | def traverse_sequences(other, callbacks = T.unsafe(nil), &block); end 71 | 72 | # Attempts to patch +self+ with the provided +patchset+. A new sequence based 73 | # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Attempts 74 | # to autodiscover the direction of the patch. 75 | # 76 | # source://diff-lcs//lib/diff/lcs.rb#101 77 | def unpatch(patchset); end 78 | 79 | # Attempts to unpatch +self+ with the provided +patchset+. A new sequence 80 | # based on +self+ and the +patchset+ will be created. See Diff::LCS#unpatch. 81 | # Does no patch direction autodiscovery. 82 | # 83 | # source://diff-lcs//lib/diff/lcs.rb#116 84 | def unpatch!(patchset); end 85 | 86 | # Attempts to unpatch +self+ with the provided +patchset+, using #unpatch!. 87 | # If the sequence this is used on supports #replace, the value of +self+ will 88 | # be replaced. See Diff::LCS#unpatch. Does no patch direction autodiscovery. 89 | # 90 | # source://diff-lcs//lib/diff/lcs.rb#134 91 | def unpatch_me(patchset); end 92 | 93 | class << self 94 | # :yields seq1[i] for each matched: 95 | # 96 | # source://diff-lcs//lib/diff/lcs.rb#144 97 | def LCS(seq1, seq2, &block); end 98 | 99 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#52 100 | def callbacks_for(callbacks); end 101 | 102 | # #diff computes the smallest set of additions and deletions necessary to 103 | # turn the first sequence into the second, and returns a description of these 104 | # changes. 105 | # 106 | # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate 107 | # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a 108 | # Class argument is provided for +callbacks+, #diff will attempt to 109 | # initialise it. If the +callbacks+ object (possibly initialised) responds to 110 | # #finish, it will be called. 111 | # 112 | # source://diff-lcs//lib/diff/lcs.rb#168 113 | def diff(seq1, seq2, callbacks = T.unsafe(nil), &block); end 114 | 115 | # :yields seq1[i] for each matched: 116 | # 117 | # source://diff-lcs//lib/diff/lcs.rb#144 118 | def lcs(seq1, seq2, &block); end 119 | 120 | # Applies a +patchset+ to the sequence +src+ according to the +direction+ 121 | # (:patch or :unpatch), producing a new sequence. 122 | # 123 | # If the +direction+ is not specified, Diff::LCS::patch will attempt to 124 | # discover the direction of the +patchset+. 125 | # 126 | # A +patchset+ can be considered to apply forward (:patch) if the 127 | # following expression is true: 128 | # 129 | # patch(s1, diff(s1, s2)) -> s2 130 | # 131 | # A +patchset+ can be considered to apply backward (:unpatch) if the 132 | # following expression is true: 133 | # 134 | # patch(s2, diff(s1, s2)) -> s1 135 | # 136 | # If the +patchset+ contains no changes, the +src+ value will be returned as 137 | # either src.dup or +src+. A +patchset+ can be deemed as having no 138 | # changes if the following predicate returns true: 139 | # 140 | # patchset.empty? or 141 | # patchset.flatten(1).all? { |change| change.unchanged? } 142 | # 143 | # === Patchsets 144 | # 145 | # A +patchset+ is always an enumerable sequence of changes, hunks of changes, 146 | # or a mix of the two. A hunk of changes is an enumerable sequence of 147 | # changes: 148 | # 149 | # [ # patchset 150 | # # change 151 | # [ # hunk 152 | # # change 153 | # ] 154 | # ] 155 | # 156 | # The +patch+ method accepts patchsets that are enumerable sequences 157 | # containing either Diff::LCS::Change objects (or a subclass) or the array 158 | # representations of those objects. Prior to application, array 159 | # representations of Diff::LCS::Change objects will be reified. 160 | # 161 | # source://diff-lcs//lib/diff/lcs.rb#624 162 | def patch(src, patchset, direction = T.unsafe(nil)); end 163 | 164 | # Given a set of patchset, convert the current version to the next version. 165 | # Does no auto-discovery. 166 | # 167 | # source://diff-lcs//lib/diff/lcs.rb#734 168 | def patch!(src, patchset); end 169 | 170 | # #sdiff computes all necessary components to show two sequences and their 171 | # minimized differences side by side, just like the Unix utility 172 | # sdiff does: 173 | # 174 | # old < - 175 | # same same 176 | # before | after 177 | # - > new 178 | # 179 | # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate 180 | # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a 181 | # Class argument is provided for +callbacks+, #diff will attempt to 182 | # initialise it. If the +callbacks+ object (possibly initialised) responds to 183 | # #finish, it will be called. 184 | # 185 | # Each element of a returned array is a Diff::LCS::ContextChange object, 186 | # which can be implicitly converted to an array. 187 | # 188 | # Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)| 189 | # case action 190 | # when '!' 191 | # # replace 192 | # when '-' 193 | # # delete 194 | # when '+' 195 | # # insert 196 | # end 197 | # end 198 | # 199 | # source://diff-lcs//lib/diff/lcs.rb#200 200 | def sdiff(seq1, seq2, callbacks = T.unsafe(nil), &block); end 201 | 202 | # #traverse_balanced is an alternative to #traverse_sequences. It uses a 203 | # different algorithm to iterate through the entries in the computed longest 204 | # common subsequence. Instead of viewing the changes as insertions or 205 | # deletions from one of the sequences, #traverse_balanced will report 206 | # changes between the sequences. 207 | # 208 | # The arguments to #traverse_balanced are the two sequences to traverse and a 209 | # callback object, like this: 210 | # 211 | # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new) 212 | # 213 | # #sdiff is implemented with #traverse_balanced. 214 | # 215 | # == Callback Methods 216 | # 217 | # Optional callback methods are emphasized. 218 | # 219 | # callbacks#match:: Called when +a+ and +b+ are pointing to 220 | # common elements in +A+ and +B+. 221 | # callbacks#discard_a:: Called when +a+ is pointing to an 222 | # element not in +B+. 223 | # callbacks#discard_b:: Called when +b+ is pointing to an 224 | # element not in +A+. 225 | # callbacks#change:: Called when +a+ and +b+ are pointing to 226 | # the same relative position, but 227 | # A[a] and B[b] are not 228 | # the same; a change has 229 | # occurred. 230 | # 231 | # #traverse_balanced might be a bit slower than #traverse_sequences, 232 | # noticable only while processing huge amounts of data. 233 | # 234 | # == Algorithm 235 | # 236 | # a---+ 237 | # v 238 | # A = a b c e h j l m n p 239 | # B = b c d e f j k l m r s t 240 | # ^ 241 | # b---+ 242 | # 243 | # === Matches 244 | # 245 | # If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+ 246 | # and +B+, the arrows will initially point to the first elements of their 247 | # respective sequences. #traverse_sequences will advance the arrows through 248 | # the sequences one element at a time, calling a method on the user-specified 249 | # callback object before each advance. It will advance the arrows in such a 250 | # way that if there are elements A[i] and B[j] which are 251 | # both equal and part of the longest common subsequence, there will be some 252 | # moment during the execution of #traverse_sequences when arrow +a+ is 253 | # pointing to A[i] and arrow +b+ is pointing to B[j]. When 254 | # this happens, #traverse_sequences will call callbacks#match and 255 | # then it will advance both arrows. 256 | # 257 | # === Discards 258 | # 259 | # Otherwise, one of the arrows is pointing to an element of its sequence that 260 | # is not part of the longest common subsequence. #traverse_sequences will 261 | # advance that arrow and will call callbacks#discard_a or 262 | # callbacks#discard_b, depending on which arrow it advanced. 263 | # 264 | # === Changes 265 | # 266 | # If both +a+ and +b+ point to elements that are not part of the longest 267 | # common subsequence, then #traverse_sequences will try to call 268 | # callbacks#change and advance both arrows. If 269 | # callbacks#change is not implemented, then 270 | # callbacks#discard_a and callbacks#discard_b will be 271 | # called in turn. 272 | # 273 | # The methods for callbacks#match, callbacks#discard_a, 274 | # callbacks#discard_b, and callbacks#change are invoked 275 | # with an event comprising the action ("=", "+", "-", or "!", respectively), 276 | # the indicies +i+ and +j+, and the elements A[i] and B[j]. 277 | # Return values are discarded by #traverse_balanced. 278 | # 279 | # === Context 280 | # 281 | # Note that +i+ and +j+ may not be the same index position, even if +a+ and 282 | # +b+ are considered to be pointing to matching or changed elements. 283 | # 284 | # source://diff-lcs//lib/diff/lcs.rb#475 285 | def traverse_balanced(seq1, seq2, callbacks = T.unsafe(nil)); end 286 | 287 | # #traverse_sequences is the most general facility provided by this module; 288 | # #diff and #lcs are implemented as calls to it. 289 | # 290 | # The arguments to #traverse_sequences are the two sequences to traverse, and 291 | # a callback object, like this: 292 | # 293 | # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new) 294 | # 295 | # == Callback Methods 296 | # 297 | # Optional callback methods are emphasized. 298 | # 299 | # callbacks#match:: Called when +a+ and +b+ are pointing to 300 | # common elements in +A+ and +B+. 301 | # callbacks#discard_a:: Called when +a+ is pointing to an 302 | # element not in +B+. 303 | # callbacks#discard_b:: Called when +b+ is pointing to an 304 | # element not in +A+. 305 | # callbacks#finished_a:: Called when +a+ has reached the end of 306 | # sequence +A+. 307 | # callbacks#finished_b:: Called when +b+ has reached the end of 308 | # sequence +B+. 309 | # 310 | # == Algorithm 311 | # 312 | # a---+ 313 | # v 314 | # A = a b c e h j l m n p 315 | # B = b c d e f j k l m r s t 316 | # ^ 317 | # b---+ 318 | # 319 | # If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+ 320 | # and +B+, the arrows will initially point to the first elements of their 321 | # respective sequences. #traverse_sequences will advance the arrows through 322 | # the sequences one element at a time, calling a method on the user-specified 323 | # callback object before each advance. It will advance the arrows in such a 324 | # way that if there are elements A[i] and B[j] which are 325 | # both equal and part of the longest common subsequence, there will be some 326 | # moment during the execution of #traverse_sequences when arrow +a+ is 327 | # pointing to A[i] and arrow +b+ is pointing to B[j]. When 328 | # this happens, #traverse_sequences will call callbacks#match and 329 | # then it will advance both arrows. 330 | # 331 | # Otherwise, one of the arrows is pointing to an element of its sequence that 332 | # is not part of the longest common subsequence. #traverse_sequences will 333 | # advance that arrow and will call callbacks#discard_a or 334 | # callbacks#discard_b, depending on which arrow it advanced. If both 335 | # arrows point to elements that are not part of the longest common 336 | # subsequence, then #traverse_sequences will advance arrow +a+ and call the 337 | # appropriate callback, then it will advance arrow +b+ and call the appropriate 338 | # callback. 339 | # 340 | # The methods for callbacks#match, callbacks#discard_a, and 341 | # callbacks#discard_b are invoked with an event comprising the 342 | # action ("=", "+", or "-", respectively), the indicies +i+ and +j+, and the 343 | # elements A[i] and B[j]. Return values are discarded by 344 | # #traverse_sequences. 345 | # 346 | # === End of Sequences 347 | # 348 | # If arrow +a+ reaches the end of its sequence before arrow +b+ does, 349 | # #traverse_sequence will try to call callbacks#finished_a with the 350 | # last index and element of +A+ (A[-1]) and the current index and 351 | # element of +B+ (B[j]). If callbacks#finished_a does not 352 | # exist, then callbacks#discard_b will be called on each element of 353 | # +B+ until the end of the sequence is reached (the call will be done with 354 | # A[-1] and B[j] for each element). 355 | # 356 | # If +b+ reaches the end of +B+ before +a+ reaches the end of +A+, 357 | # callbacks#finished_b will be called with the current index and 358 | # element of +A+ (A[i]) and the last index and element of +B+ 359 | # (A[-1]). Again, if callbacks#finished_b does not exist on 360 | # the callback object, then callbacks#discard_a will be called on 361 | # each element of +A+ until the end of the sequence is reached (A[i] 362 | # and B[-1]). 363 | # 364 | # There is a chance that one additional callbacks#discard_a or 365 | # callbacks#discard_b will be called after the end of the sequence 366 | # is reached, if +a+ has not yet reached the end of +A+ or +b+ has not yet 367 | # reached the end of +B+. 368 | # 369 | # source://diff-lcs//lib/diff/lcs.rb#285 370 | def traverse_sequences(seq1, seq2, callbacks = T.unsafe(nil)); end 371 | 372 | # Given a set of patchset, convert the current version to the prior version. 373 | # Does no auto-discovery. 374 | # 375 | # source://diff-lcs//lib/diff/lcs.rb#728 376 | def unpatch!(src, patchset); end 377 | 378 | private 379 | 380 | # source://diff-lcs//lib/diff/lcs/internals.rb#4 381 | def diff_traversal(method, seq1, seq2, callbacks, &block); end 382 | end 383 | end 384 | 385 | # An alias for DefaultCallbacks that is used in 386 | # Diff::LCS#traverse_balanced. 387 | # 388 | # Diff::LCS.LCS(seq1, seq2, Diff::LCS::BalancedCallbacks) 389 | # 390 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#50 391 | Diff::LCS::BalancedCallbacks = Diff::LCS::DefaultCallbacks 392 | 393 | # A block is an operation removing, adding, or changing a group of items. 394 | # Basically, this is just a list of changes, where each change adds or 395 | # deletes a single item. Used by bin/ldiff. 396 | # 397 | # source://diff-lcs//lib/diff/lcs/block.rb#6 398 | class Diff::LCS::Block 399 | # @return [Block] a new instance of Block 400 | # 401 | # source://diff-lcs//lib/diff/lcs/block.rb#9 402 | def initialize(chunk); end 403 | 404 | # Returns the value of attribute changes. 405 | # 406 | # source://diff-lcs//lib/diff/lcs/block.rb#7 407 | def changes; end 408 | 409 | # source://diff-lcs//lib/diff/lcs/block.rb#21 410 | def diff_size; end 411 | 412 | # Returns the value of attribute insert. 413 | # 414 | # source://diff-lcs//lib/diff/lcs/block.rb#7 415 | def insert; end 416 | 417 | # source://diff-lcs//lib/diff/lcs/block.rb#25 418 | def op; end 419 | 420 | # Returns the value of attribute remove. 421 | # 422 | # source://diff-lcs//lib/diff/lcs/block.rb#7 423 | def remove; end 424 | end 425 | 426 | # Represents a simplistic (non-contextual) change. Represents the removal or 427 | # addition of an element from either the old or the new sequenced 428 | # enumerable. 429 | # 430 | # source://diff-lcs//lib/diff/lcs/change.rb#6 431 | class Diff::LCS::Change 432 | include ::Comparable 433 | 434 | # @return [Change] a new instance of Change 435 | # 436 | # source://diff-lcs//lib/diff/lcs/change.rb#27 437 | def initialize(*args); end 438 | 439 | # source://diff-lcs//lib/diff/lcs/change.rb#65 440 | def <=>(other); end 441 | 442 | # source://diff-lcs//lib/diff/lcs/change.rb#58 443 | def ==(other); end 444 | 445 | # Returns the action this Change represents. 446 | # 447 | # source://diff-lcs//lib/diff/lcs/change.rb#20 448 | def action; end 449 | 450 | # @return [Boolean] 451 | # 452 | # source://diff-lcs//lib/diff/lcs/change.rb#72 453 | def adding?; end 454 | 455 | # @return [Boolean] 456 | # 457 | # source://diff-lcs//lib/diff/lcs/change.rb#84 458 | def changed?; end 459 | 460 | # @return [Boolean] 461 | # 462 | # source://diff-lcs//lib/diff/lcs/change.rb#76 463 | def deleting?; end 464 | 465 | # Returns the sequence element of the Change. 466 | # 467 | # source://diff-lcs//lib/diff/lcs/change.rb#25 468 | def element; end 469 | 470 | # @return [Boolean] 471 | # 472 | # source://diff-lcs//lib/diff/lcs/change.rb#88 473 | def finished_a?; end 474 | 475 | # @return [Boolean] 476 | # 477 | # source://diff-lcs//lib/diff/lcs/change.rb#92 478 | def finished_b?; end 479 | 480 | # source://diff-lcs//lib/diff/lcs/change.rb#34 481 | def inspect(*_args); end 482 | 483 | # Returns the position of the Change. 484 | # 485 | # source://diff-lcs//lib/diff/lcs/change.rb#23 486 | def position; end 487 | 488 | # source://diff-lcs//lib/diff/lcs/change.rb#38 489 | def to_a; end 490 | 491 | # source://diff-lcs//lib/diff/lcs/change.rb#38 492 | def to_ary; end 493 | 494 | # @return [Boolean] 495 | # 496 | # source://diff-lcs//lib/diff/lcs/change.rb#80 497 | def unchanged?; end 498 | 499 | class << self 500 | # source://diff-lcs//lib/diff/lcs/change.rb#44 501 | def from_a(arr); end 502 | 503 | # @return [Boolean] 504 | # 505 | # source://diff-lcs//lib/diff/lcs/change.rb#15 506 | def valid_action?(action); end 507 | end 508 | end 509 | 510 | # source://diff-lcs//lib/diff/lcs/change.rb#7 511 | Diff::LCS::Change::IntClass = Integer 512 | 513 | # The only actions valid for changes are '+' (add), '-' (delete), '=' 514 | # (no change), '!' (changed), '<' (tail changes from first sequence), or 515 | # '>' (tail changes from second sequence). The last two ('<>') are only 516 | # found with Diff::LCS::diff and Diff::LCS::sdiff. 517 | # 518 | # source://diff-lcs//lib/diff/lcs/change.rb#13 519 | Diff::LCS::Change::VALID_ACTIONS = T.let(T.unsafe(nil), Array) 520 | 521 | # Represents a contextual change. Contains the position and values of the 522 | # elements in the old and the new sequenced enumerables as well as the action 523 | # taken. 524 | # 525 | # source://diff-lcs//lib/diff/lcs/change.rb#101 526 | class Diff::LCS::ContextChange < ::Diff::LCS::Change 527 | # @return [ContextChange] a new instance of ContextChange 528 | # 529 | # source://diff-lcs//lib/diff/lcs/change.rb#114 530 | def initialize(*args); end 531 | 532 | # source://diff-lcs//lib/diff/lcs/change.rb#166 533 | def <=>(other); end 534 | 535 | # source://diff-lcs//lib/diff/lcs/change.rb#157 536 | def ==(other); end 537 | 538 | # Returns the new element being changed. 539 | # 540 | # source://diff-lcs//lib/diff/lcs/change.rb#112 541 | def new_element; end 542 | 543 | # Returns the new position being changed. 544 | # 545 | # source://diff-lcs//lib/diff/lcs/change.rb#108 546 | def new_position; end 547 | 548 | # Returns the old element being changed. 549 | # 550 | # source://diff-lcs//lib/diff/lcs/change.rb#110 551 | def old_element; end 552 | 553 | # Returns the old position being changed. 554 | # 555 | # source://diff-lcs//lib/diff/lcs/change.rb#106 556 | def old_position; end 557 | 558 | # source://diff-lcs//lib/diff/lcs/change.rb#122 559 | def to_a; end 560 | 561 | # source://diff-lcs//lib/diff/lcs/change.rb#122 562 | def to_ary; end 563 | 564 | class << self 565 | # source://diff-lcs//lib/diff/lcs/change.rb#132 566 | def from_a(arr); end 567 | 568 | # Simplifies a context change for use in some diff callbacks. '<' actions 569 | # are converted to '-' and '>' actions are converted to '+'. 570 | # 571 | # source://diff-lcs//lib/diff/lcs/change.rb#138 572 | def simplify(event); end 573 | end 574 | end 575 | 576 | # This will produce a compound array of contextual diff change objects. Each 577 | # element in the #diffs array is a "hunk" array, where each element in each 578 | # "hunk" array is a single change. Each change is a Diff::LCS::ContextChange 579 | # that contains both the old index and new index values for the change. The 580 | # "hunk" provides the full context for the changes. Both old and new objects 581 | # will be presented for changed objects. +nil+ will be substituted for a 582 | # discarded object. 583 | # 584 | # seq1 = %w(a b c e h j l m n p) 585 | # seq2 = %w(b c d e f j k l m r s t) 586 | # 587 | # diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) 588 | # # This example shows a simplified array format. 589 | # # [ [ [ '-', [ 0, 'a' ], [ 0, nil ] ] ], # 1 590 | # # [ [ '+', [ 3, nil ], [ 2, 'd' ] ] ], # 2 591 | # # [ [ '-', [ 4, 'h' ], [ 4, nil ] ], # 3 592 | # # [ '+', [ 5, nil ], [ 4, 'f' ] ] ], 593 | # # [ [ '+', [ 6, nil ], [ 6, 'k' ] ] ], # 4 594 | # # [ [ '-', [ 8, 'n' ], [ 9, nil ] ], # 5 595 | # # [ '+', [ 9, nil ], [ 9, 'r' ] ], 596 | # # [ '-', [ 9, 'p' ], [ 10, nil ] ], 597 | # # [ '+', [ 10, nil ], [ 10, 's' ] ], 598 | # # [ '+', [ 10, nil ], [ 11, 't' ] ] ] ] 599 | # 600 | # The five hunks shown are comprised of individual changes; if there is a 601 | # related set of changes, they are still shown individually. 602 | # 603 | # This callback can also be used with Diff::LCS#sdiff, which will produce 604 | # results like: 605 | # 606 | # diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks) 607 | # # This example shows a simplified array format. 608 | # # [ [ [ "-", [ 0, "a" ], [ 0, nil ] ] ], # 1 609 | # # [ [ "+", [ 3, nil ], [ 2, "d" ] ] ], # 2 610 | # # [ [ "!", [ 4, "h" ], [ 4, "f" ] ] ], # 3 611 | # # [ [ "+", [ 6, nil ], [ 6, "k" ] ] ], # 4 612 | # # [ [ "!", [ 8, "n" ], [ 9, "r" ] ], # 5 613 | # # [ "!", [ 9, "p" ], [ 10, "s" ] ], 614 | # # [ "+", [ 10, nil ], [ 11, "t" ] ] ] ] 615 | # 616 | # The five hunks are still present, but are significantly shorter in total 617 | # presentation, because changed items are shown as changes ("!") instead of 618 | # potentially "mismatched" pairs of additions and deletions. 619 | # 620 | # The result of this operation is similar to that of 621 | # Diff::LCS::SDiffCallbacks. They may be compared as: 622 | # 623 | # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" } 624 | # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1) 625 | # 626 | # s == c # -> true 627 | # 628 | # === Use 629 | # 630 | # This callback object must be initialised and can be used by the 631 | # Diff::LCS#diff or Diff::LCS#sdiff methods. 632 | # 633 | # cbo = Diff::LCS::ContextDiffCallbacks.new 634 | # Diff::LCS.LCS(seq1, seq2, cbo) 635 | # cbo.finish 636 | # 637 | # Note that the call to #finish is absolutely necessary, or the last set of 638 | # changes will not be visible. Alternatively, can be used as: 639 | # 640 | # cbo = Diff::LCS::ContextDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } 641 | # 642 | # The necessary #finish call will be made. 643 | # 644 | # === Simplified Array Format 645 | # 646 | # The simplified array format used in the example above can be obtained 647 | # with: 648 | # 649 | # require 'pp' 650 | # pp diffs.map { |e| e.map { |f| f.to_a } } 651 | # 652 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#223 653 | class Diff::LCS::ContextDiffCallbacks < ::Diff::LCS::DiffCallbacks 654 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#232 655 | def change(event); end 656 | 657 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#224 658 | def discard_a(event); end 659 | 660 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#228 661 | def discard_b(event); end 662 | end 663 | 664 | # This callback object implements the default set of callback events, 665 | # which only returns the event itself. Note that #finished_a and 666 | # #finished_b are not implemented -- I haven't yet figured out where they 667 | # would be useful. 668 | # 669 | # Note that this is intended to be called as is, e.g., 670 | # 671 | # Diff::LCS.LCS(seq1, seq2, Diff::LCS::DefaultCallbacks) 672 | # 673 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#14 674 | class Diff::LCS::DefaultCallbacks 675 | class << self 676 | # Called when both the old and new values have changed. 677 | # 678 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#32 679 | def change(event); end 680 | 681 | # Called when the old value is discarded in favour of the new value. 682 | # 683 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#22 684 | def discard_a(event); end 685 | 686 | # Called when the new value is discarded in favour of the old value. 687 | # 688 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#27 689 | def discard_b(event); end 690 | 691 | # Called when two items match. 692 | # 693 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#17 694 | def match(event); end 695 | end 696 | end 697 | 698 | # This will produce a compound array of simple diff change objects. Each 699 | # element in the #diffs array is a +hunk+ or +hunk+ array, where each 700 | # element in each +hunk+ array is a single Change object representing the 701 | # addition or removal of a single element from one of the two tested 702 | # sequences. The +hunk+ provides the full context for the changes. 703 | # 704 | # diffs = Diff::LCS.diff(seq1, seq2) 705 | # # This example shows a simplified array format. 706 | # # [ [ [ '-', 0, 'a' ] ], # 1 707 | # # [ [ '+', 2, 'd' ] ], # 2 708 | # # [ [ '-', 4, 'h' ], # 3 709 | # # [ '+', 4, 'f' ] ], 710 | # # [ [ '+', 6, 'k' ] ], # 4 711 | # # [ [ '-', 8, 'n' ], # 5 712 | # # [ '-', 9, 'p' ], 713 | # # [ '+', 9, 'r' ], 714 | # # [ '+', 10, 's' ], 715 | # # [ '+', 11, 't' ] ] ] 716 | # 717 | # There are five hunks here. The first hunk says that the +a+ at position 0 718 | # of the first sequence should be deleted ('-'). The second hunk 719 | # says that the +d+ at position 2 of the second sequence should be inserted 720 | # ('+'). The third hunk says that the +h+ at position 4 of the 721 | # first sequence should be removed and replaced with the +f+ from position 4 722 | # of the second sequence. The other two hunks are described similarly. 723 | # 724 | # === Use 725 | # 726 | # This callback object must be initialised and is used by the Diff::LCS#diff 727 | # method. 728 | # 729 | # cbo = Diff::LCS::DiffCallbacks.new 730 | # Diff::LCS.LCS(seq1, seq2, cbo) 731 | # cbo.finish 732 | # 733 | # Note that the call to #finish is absolutely necessary, or the last set of 734 | # changes will not be visible. Alternatively, can be used as: 735 | # 736 | # cbo = Diff::LCS::DiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } 737 | # 738 | # The necessary #finish call will be made. 739 | # 740 | # === Simplified Array Format 741 | # 742 | # The simplified array format used in the example above can be obtained 743 | # with: 744 | # 745 | # require 'pp' 746 | # pp diffs.map { |e| e.map { |f| f.to_a } } 747 | # 748 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#106 749 | class Diff::LCS::DiffCallbacks 750 | # :yields self: 751 | # 752 | # @return [DiffCallbacks] a new instance of DiffCallbacks 753 | # 754 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#110 755 | def initialize; end 756 | 757 | # Returns the difference set collected during the diff process. 758 | # 759 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#108 760 | def diffs; end 761 | 762 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#133 763 | def discard_a(event); end 764 | 765 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#137 766 | def discard_b(event); end 767 | 768 | # Finalizes the diff process. If an unprocessed hunk still exists, then it 769 | # is appended to the diff list. 770 | # 771 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#125 772 | def finish; end 773 | 774 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#129 775 | def match(_event); end 776 | 777 | private 778 | 779 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#141 780 | def finish_hunk; end 781 | end 782 | 783 | # A Hunk is a group of Blocks which overlap because of the context surrounding 784 | # each block. (So if we're not using context, every hunk will contain one 785 | # block.) Used in the diff program (bin/ldiff). 786 | # 787 | # source://diff-lcs//lib/diff/lcs/hunk.rb#8 788 | class Diff::LCS::Hunk 789 | # Create a hunk using references to both the old and new data, as well as the 790 | # piece of data. 791 | # 792 | # @return [Hunk] a new instance of Hunk 793 | # 794 | # source://diff-lcs//lib/diff/lcs/hunk.rb#16 795 | def initialize(data_old, data_new, piece, flag_context, file_length_difference); end 796 | 797 | # Returns the value of attribute blocks. 798 | # 799 | # source://diff-lcs//lib/diff/lcs/hunk.rb#63 800 | def blocks; end 801 | 802 | # Returns a diff string based on a format. 803 | # 804 | # source://diff-lcs//lib/diff/lcs/hunk.rb#116 805 | def diff(format, last = T.unsafe(nil)); end 806 | 807 | # Returns the value of attribute end_new. 808 | # 809 | # source://diff-lcs//lib/diff/lcs/hunk.rb#65 810 | def end_new; end 811 | 812 | # Returns the value of attribute end_old. 813 | # 814 | # source://diff-lcs//lib/diff/lcs/hunk.rb#65 815 | def end_old; end 816 | 817 | # Returns the value of attribute file_length_difference. 818 | # 819 | # source://diff-lcs//lib/diff/lcs/hunk.rb#66 820 | def file_length_difference; end 821 | 822 | # Change the "start" and "end" fields to note that context should be added 823 | # to this hunk. 824 | # 825 | # source://diff-lcs//lib/diff/lcs/hunk.rb#70 826 | def flag_context; end 827 | 828 | # source://diff-lcs//lib/diff/lcs/hunk.rb#72 829 | def flag_context=(context); end 830 | 831 | # Merges this hunk and the provided hunk together if they overlap. Returns 832 | # a truthy value so that if there is no overlap, you can know the merge 833 | # was skipped. 834 | # 835 | # source://diff-lcs//lib/diff/lcs/hunk.rb#98 836 | def merge(hunk); end 837 | 838 | # @return [Boolean] 839 | # 840 | # source://diff-lcs//lib/diff/lcs/hunk.rb#326 841 | def missing_last_newline?(data); end 842 | 843 | # Determines whether there is an overlap between this hunk and the 844 | # provided hunk. This will be true if the difference between the two hunks 845 | # start or end positions is within one position of each other. 846 | # 847 | # @return [Boolean] 848 | # 849 | # source://diff-lcs//lib/diff/lcs/hunk.rb#110 850 | def overlaps?(hunk); end 851 | 852 | # Returns the value of attribute start_new. 853 | # 854 | # source://diff-lcs//lib/diff/lcs/hunk.rb#64 855 | def start_new; end 856 | 857 | # Returns the value of attribute start_old. 858 | # 859 | # source://diff-lcs//lib/diff/lcs/hunk.rb#64 860 | def start_old; end 861 | 862 | # Merges this hunk and the provided hunk together if they overlap. Returns 863 | # a truthy value so that if there is no overlap, you can know the merge 864 | # was skipped. 865 | # 866 | # source://diff-lcs//lib/diff/lcs/hunk.rb#98 867 | def unshift(hunk); end 868 | 869 | private 870 | 871 | # source://diff-lcs//lib/diff/lcs/hunk.rb#213 872 | def context_diff(last = T.unsafe(nil)); end 873 | 874 | # Generate a range of item numbers to print. Only print 1 number if the 875 | # range has only one item in it. Otherwise, it's 'start,end' 876 | # 877 | # source://diff-lcs//lib/diff/lcs/hunk.rb#293 878 | def context_range(mode, op, last = T.unsafe(nil)); end 879 | 880 | # source://diff-lcs//lib/diff/lcs/hunk.rb#271 881 | def ed_diff(format, _last = T.unsafe(nil)); end 882 | 883 | # source://diff-lcs//lib/diff/lcs/hunk.rb#339 884 | def encode(literal, target_encoding = T.unsafe(nil)); end 885 | 886 | # source://diff-lcs//lib/diff/lcs/hunk.rb#343 887 | def encode_as(string, *args); end 888 | 889 | # Note that an old diff can't have any context. Therefore, we know that 890 | # there's only one block in the hunk. 891 | # 892 | # source://diff-lcs//lib/diff/lcs/hunk.rb#135 893 | def old_diff(_last = T.unsafe(nil)); end 894 | 895 | # source://diff-lcs//lib/diff/lcs/hunk.rb#160 896 | def unified_diff(last = T.unsafe(nil)); end 897 | 898 | # Generate a range of item numbers to print for unified diff. Print number 899 | # where block starts, followed by number of lines in the block 900 | # (don't print number of lines if it's 1) 901 | # 902 | # source://diff-lcs//lib/diff/lcs/hunk.rb#311 903 | def unified_range(mode, last); end 904 | end 905 | 906 | # source://diff-lcs//lib/diff/lcs/hunk.rb#10 907 | Diff::LCS::Hunk::ED_DIFF_OP_ACTION = T.let(T.unsafe(nil), Hash) 908 | 909 | # source://diff-lcs//lib/diff/lcs/hunk.rb#9 910 | Diff::LCS::Hunk::OLD_DIFF_OP_ACTION = T.let(T.unsafe(nil), Hash) 911 | 912 | # source://diff-lcs//lib/diff/lcs/internals.rb#29 913 | module Diff::LCS::Internals 914 | class << self 915 | # This method will analyze the provided patchset to provide a single-pass 916 | # normalization (conversion of the array form of Diff::LCS::Change objects to 917 | # the object form of same) and detection of whether the patchset represents 918 | # changes to be made. 919 | # 920 | # source://diff-lcs//lib/diff/lcs/internals.rb#102 921 | def analyze_patchset(patchset, depth = T.unsafe(nil)); end 922 | 923 | # Examine the patchset and the source to see in which direction the 924 | # patch should be applied. 925 | # 926 | # WARNING: By default, this examines the whole patch, so this could take 927 | # some time. This also works better with Diff::LCS::ContextChange or 928 | # Diff::LCS::Change as its source, as an array will cause the creation 929 | # of one of the above. 930 | # 931 | # source://diff-lcs//lib/diff/lcs/internals.rb#147 932 | def intuit_diff_direction(src, patchset, limit = T.unsafe(nil)); end 933 | 934 | # Compute the longest common subsequence between the sequenced 935 | # Enumerables +a+ and +b+. The result is an array whose contents is such 936 | # that 937 | # 938 | # result = Diff::LCS::Internals.lcs(a, b) 939 | # result.each_with_index do |e, i| 940 | # assert_equal(a[i], b[e]) unless e.nil? 941 | # end 942 | # 943 | # source://diff-lcs//lib/diff/lcs/internals.rb#41 944 | def lcs(a, b); end 945 | 946 | private 947 | 948 | # If +vector+ maps the matching elements of another collection onto this 949 | # Enumerable, compute the inverse of +vector+ that maps this Enumerable 950 | # onto the collection. (Currently unused.) 951 | # 952 | # source://diff-lcs//lib/diff/lcs/internals.rb#286 953 | def inverse_vector(a, vector); end 954 | 955 | # Returns a hash mapping each element of an Enumerable to the set of 956 | # positions it occupies in the Enumerable, optionally restricted to the 957 | # elements specified in the range of indexes specified by +interval+. 958 | # 959 | # source://diff-lcs//lib/diff/lcs/internals.rb#298 960 | def position_hash(enum, interval); end 961 | 962 | # Find the place at which +value+ would normally be inserted into the 963 | # Enumerable. If that place is already occupied by +value+, do nothing 964 | # and return +nil+. If the place does not exist (i.e., it is off the end 965 | # of the Enumerable), add it to the end. Otherwise, replace the element 966 | # at that point with +value+. It is assumed that the Enumerable's values 967 | # are numeric. 968 | # 969 | # This operation preserves the sort order. 970 | # 971 | # source://diff-lcs//lib/diff/lcs/internals.rb#252 972 | def replace_next_larger(enum, value, last_index = T.unsafe(nil)); end 973 | end 974 | end 975 | 976 | # This will produce a simple array of diff change objects. Each element in 977 | # the #diffs array is a single ContextChange. In the set of #diffs provided 978 | # by SDiffCallbacks, both old and new objects will be presented for both 979 | # changed and unchanged objects. +nil+ will be substituted 980 | # for a discarded object. 981 | # 982 | # The diffset produced by this callback, when provided to Diff::LCS#sdiff, 983 | # will compute and display the necessary components to show two sequences 984 | # and their minimized differences side by side, just like the Unix utility 985 | # +sdiff+. 986 | # 987 | # same same 988 | # before | after 989 | # old < - 990 | # - > new 991 | # 992 | # seq1 = %w(a b c e h j l m n p) 993 | # seq2 = %w(b c d e f j k l m r s t) 994 | # 995 | # diffs = Diff::LCS.sdiff(seq1, seq2) 996 | # # This example shows a simplified array format. 997 | # # [ [ "-", [ 0, "a"], [ 0, nil ] ], 998 | # # [ "=", [ 1, "b"], [ 0, "b" ] ], 999 | # # [ "=", [ 2, "c"], [ 1, "c" ] ], 1000 | # # [ "+", [ 3, nil], [ 2, "d" ] ], 1001 | # # [ "=", [ 3, "e"], [ 3, "e" ] ], 1002 | # # [ "!", [ 4, "h"], [ 4, "f" ] ], 1003 | # # [ "=", [ 5, "j"], [ 5, "j" ] ], 1004 | # # [ "+", [ 6, nil], [ 6, "k" ] ], 1005 | # # [ "=", [ 6, "l"], [ 7, "l" ] ], 1006 | # # [ "=", [ 7, "m"], [ 8, "m" ] ], 1007 | # # [ "!", [ 8, "n"], [ 9, "r" ] ], 1008 | # # [ "!", [ 9, "p"], [ 10, "s" ] ], 1009 | # # [ "+", [ 10, nil], [ 11, "t" ] ] ] 1010 | # 1011 | # The result of this operation is similar to that of 1012 | # Diff::LCS::ContextDiffCallbacks. They may be compared as: 1013 | # 1014 | # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" } 1015 | # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1) 1016 | # 1017 | # s == c # -> true 1018 | # 1019 | # === Use 1020 | # 1021 | # This callback object must be initialised and is used by the Diff::LCS#sdiff 1022 | # method. 1023 | # 1024 | # cbo = Diff::LCS::SDiffCallbacks.new 1025 | # Diff::LCS.LCS(seq1, seq2, cbo) 1026 | # 1027 | # As with the other initialisable callback objects, 1028 | # Diff::LCS::SDiffCallbacks can be initialised with a block. As there is no 1029 | # "fininishing" to be done, this has no effect on the state of the object. 1030 | # 1031 | # cbo = Diff::LCS::SDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } 1032 | # 1033 | # === Simplified Array Format 1034 | # 1035 | # The simplified array format used in the example above can be obtained 1036 | # with: 1037 | # 1038 | # require 'pp' 1039 | # pp diffs.map { |e| e.to_a } 1040 | # 1041 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#301 1042 | class Diff::LCS::SDiffCallbacks 1043 | # :yields self: 1044 | # 1045 | # @return [SDiffCallbacks] a new instance of SDiffCallbacks 1046 | # @yield [_self] 1047 | # @yieldparam _self [Diff::LCS::SDiffCallbacks] the object that the method was called on 1048 | # 1049 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#305 1050 | def initialize; end 1051 | 1052 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#322 1053 | def change(event); end 1054 | 1055 | # Returns the difference set collected during the diff process. 1056 | # 1057 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#303 1058 | def diffs; end 1059 | 1060 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#314 1061 | def discard_a(event); end 1062 | 1063 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#318 1064 | def discard_b(event); end 1065 | 1066 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#310 1067 | def match(event); end 1068 | end 1069 | 1070 | # An alias for DefaultCallbacks that is used in 1071 | # Diff::LCS#traverse_sequences. 1072 | # 1073 | # Diff::LCS.LCS(seq1, seq2, Diff::LCS::SequenceCallbacks) 1074 | # 1075 | # source://diff-lcs//lib/diff/lcs/callbacks.rb#44 1076 | Diff::LCS::SequenceCallbacks = Diff::LCS::DefaultCallbacks 1077 | 1078 | # source://diff-lcs//lib/diff/lcs.rb#52 1079 | Diff::LCS::VERSION = T.let(T.unsafe(nil), String) 1080 | --------------------------------------------------------------------------------