diff --git a/bridgetown-core/lib/bridgetown-core.rb b/bridgetown-core/lib/bridgetown-core.rb index 2da038198..2823570f2 100644 --- a/bridgetown-core/lib/bridgetown-core.rb +++ b/bridgetown-core/lib/bridgetown-core.rb @@ -73,6 +73,8 @@ def to_liquid class Rb < String; end module Bridgetown + using Bridgetown::Refinements + autoload :Cache, "bridgetown-core/cache" autoload :Current, "bridgetown-core/current" autoload :Cleaner, "bridgetown-core/cleaner" diff --git a/bridgetown-core/lib/bridgetown-core/collection.rb b/bridgetown-core/lib/bridgetown-core/collection.rb index 39c8dd002..367f0a16f 100644 --- a/bridgetown-core/lib/bridgetown-core/collection.rb +++ b/bridgetown-core/lib/bridgetown-core/collection.rb @@ -2,6 +2,7 @@ module Bridgetown class Collection + using Bridgetown::Refinements include Enumerable # @return [Bridgetown::Site] diff --git a/bridgetown-core/lib/bridgetown-core/commands/concerns/actions.rb b/bridgetown-core/lib/bridgetown-core/commands/concerns/actions.rb index f8ab5c038..886a8d6e7 100644 --- a/bridgetown-core/lib/bridgetown-core/commands/concerns/actions.rb +++ b/bridgetown-core/lib/bridgetown-core/commands/concerns/actions.rb @@ -3,6 +3,8 @@ module Bridgetown module Commands module Actions + using Bridgetown::Refinements + GITHUB_REGEX = %r!https://github\.com! GITHUB_TREE_REGEX = %r!#{GITHUB_REGEX}/.*/.*/tree/.*/?! GITHUB_BLOB_REGEX = %r!#{GITHUB_REGEX}/.*/.*/blob/! diff --git a/bridgetown-core/lib/bridgetown-core/commands/console.rb b/bridgetown-core/lib/bridgetown-core/commands/console.rb index 9b1e43bca..8203b1da3 100644 --- a/bridgetown-core/lib/bridgetown-core/commands/console.rb +++ b/bridgetown-core/lib/bridgetown-core/commands/console.rb @@ -103,6 +103,7 @@ def console # rubocop:disable Metrics Bridgetown.logger.info "", "You can also access #{"collections".cyan} or perform a " \ "#{"reload!".cyan}" + Bridgetown.logger.info "", "For extra Ruby smarts: #{"using Bridgetown::Refinements".cyan}" trap("SIGINT") do irb.signal_handle diff --git a/bridgetown-core/lib/bridgetown-core/component.rb b/bridgetown-core/lib/bridgetown-core/component.rb index 1120db878..16c1b584c 100644 --- a/bridgetown-core/lib/bridgetown-core/component.rb +++ b/bridgetown-core/lib/bridgetown-core/component.rb @@ -2,6 +2,7 @@ module Bridgetown class Component + using Bridgetown::Refinements include Bridgetown::Streamlined extend Forwardable @@ -120,7 +121,7 @@ def slot(name, input = nil, replace: false, &block) content = block.nil? ? input.to_s : view_context.capture(&block) name = name.to_s - slots.reject! { _1.name == name } if replace + slots.reject!(&:name.(:==, name)) if replace slots << Slot.new(name:, content:, context: self, transform: false) diff --git a/bridgetown-core/lib/bridgetown-core/concerns/site/fast_refreshable.rb b/bridgetown-core/lib/bridgetown-core/concerns/site/fast_refreshable.rb index b7827cb26..84df0404b 100644 --- a/bridgetown-core/lib/bridgetown-core/concerns/site/fast_refreshable.rb +++ b/bridgetown-core/lib/bridgetown-core/concerns/site/fast_refreshable.rb @@ -2,6 +2,8 @@ class Bridgetown::Site module FastRefreshable + using Bridgetown::Refinements + def fast_refresh(paths = [], reload_if_needed: false) # rubocop:todo Metrics FileUtils.rm_f(Bridgetown.build_errors_path) @@ -132,7 +134,7 @@ def mark_original_page_resources_for_fast_refresh(pages) end def transform_resources_for_fast_refresh(marked_resources, found_gen_pages) - marked_resources.each { _1.transform!.write } + marked_resources.each(&:transform!.(:write)) number_of_resources = marked_resources.length number_of_resources += 1 if found_gen_pages Bridgetown.logger.info( diff --git a/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb b/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb index 7c8302cb3..de0399e5a 100644 --- a/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb +++ b/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb @@ -3,6 +3,7 @@ module Bridgetown module Drops class RelationsDrop < Drop + using Bridgetown::Refinements mutable false def [](type) diff --git a/bridgetown-core/lib/bridgetown-core/helpers.rb b/bridgetown-core/lib/bridgetown-core/helpers.rb index 4d9be29e2..963b1f2dd 100644 --- a/bridgetown-core/lib/bridgetown-core/helpers.rb +++ b/bridgetown-core/lib/bridgetown-core/helpers.rb @@ -6,6 +6,7 @@ module Bridgetown class RubyTemplateView class Helpers + using Bridgetown::Refinements include Bridgetown::Filters include Bridgetown::Filters::FromLiquid include ::Streamlined::Helpers @@ -208,7 +209,7 @@ def slot(name, input = nil, replace: false, transform: true, &block) end name = name.to_s - resource.slots.reject! { _1.name == name } if replace + resource.slots.reject!(&:name.(:==, name)) if replace resource.slots << Slot.new( name:, content:, diff --git a/bridgetown-core/lib/bridgetown-core/model/repo_origin.rb b/bridgetown-core/lib/bridgetown-core/model/repo_origin.rb index a456cce22..5a5c0d1f7 100644 --- a/bridgetown-core/lib/bridgetown-core/model/repo_origin.rb +++ b/bridgetown-core/lib/bridgetown-core/model/repo_origin.rb @@ -3,6 +3,7 @@ module Bridgetown module Model class RepoOrigin < Origin + using Bridgetown::Refinements include Bridgetown::FrontMatter::Importer # @return [String] diff --git a/bridgetown-core/lib/bridgetown-core/resource/relations.rb b/bridgetown-core/lib/bridgetown-core/resource/relations.rb index fd2eb44c8..6d65c2b92 100644 --- a/bridgetown-core/lib/bridgetown-core/resource/relations.rb +++ b/bridgetown-core/lib/bridgetown-core/resource/relations.rb @@ -3,6 +3,8 @@ module Bridgetown module Resource class Relations + using Bridgetown::Refinements + # @return [Bridgetown::Resource::Base] attr_reader :resource diff --git a/bridgetown-core/lib/bridgetown-core/ruby_template_view.rb b/bridgetown-core/lib/bridgetown-core/ruby_template_view.rb index e5693ce48..260873f2b 100644 --- a/bridgetown-core/lib/bridgetown-core/ruby_template_view.rb +++ b/bridgetown-core/lib/bridgetown-core/ruby_template_view.rb @@ -22,6 +22,7 @@ def helper(name, &helper_block) class RubyTemplateView require "bridgetown-core/helpers" + using Bridgetown::Refinements include Bridgetown::Streamlined attr_reader :layout, :resource, :paginator, :site, :content @@ -129,7 +130,7 @@ def _liquid_context end def _partial_path(partial_name, ext) - partial_name = partial_name.split("/").tap { _1.last.prepend("_") }.join("/") + partial_name = partial_name.split("/").tap(&:last.(:prepend, "_")).join("/") # TODO: see if there's a workaround for this to speed up performance site.in_source_dir(site.config[:partials_dir], "#{partial_name}.#{ext}") diff --git a/bridgetown-core/lib/bridgetown-core/slot.rb b/bridgetown-core/lib/bridgetown-core/slot.rb index 53895d9dd..5d12585a5 100644 --- a/bridgetown-core/lib/bridgetown-core/slot.rb +++ b/bridgetown-core/lib/bridgetown-core/slot.rb @@ -2,6 +2,8 @@ module Bridgetown class Slot + using Bridgetown::Refinements + include Transformable # @return [String] @@ -33,7 +35,7 @@ def converters context.transformer.send(:converters) : context.send(:converters) - document_converters.select { _1.class.supports_slots? } + document_converters.select(&:class.(:supports_slots?)) end end end diff --git a/bridgetown-foundation/benchmark/refinements.rb b/bridgetown-foundation/benchmark/refinements.rb new file mode 100644 index 000000000..4de3e2260 --- /dev/null +++ b/bridgetown-foundation/benchmark/refinements.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "benchmark" + +class TestInclusion + module StringAdder + def included_add(num) + to_i + num + end + end + + String.include StringAdder + + def self.perform + raise "crash!" unless "10".included_add(20) == 30 + end +end + +module StringAdderRefinement + refine String do + def refined_add(num) + to_i + num + end + end +end + +class TestRefinement + using StringAdderRefinement + + def self.perform + raise "crash!" unless "10".refined_add(20) == 30 + end +end + +raise "Unconfirmed!" unless "".respond_to?(:included_add) +raise "Unconfirmed!" if "".respond_to?(:refined_add) + +n = 1_000_000 +Benchmark.bmbm(12) do |x| + x.report("inclusion:") do + n.times do + TestInclusion.perform + end + end + x.report("refinements:") do + n.times do + TestRefinement.perform + end + end +end diff --git a/bridgetown-foundation/lib/bridgetown-foundation.rb b/bridgetown-foundation/lib/bridgetown-foundation.rb index 6110f15d0..f84499b41 100644 --- a/bridgetown-foundation/lib/bridgetown-foundation.rb +++ b/bridgetown-foundation/lib/bridgetown-foundation.rb @@ -3,39 +3,33 @@ require "bridgetown/foundation/version" require "zeitwerk" -class Module - # Due to Active Support incompatibility, we can't extend Gem::Deprecate directly in `Object` - # So we're pulling this in as `gem_deprecate` from `deprecate`: - # https://github.com/rubygems/rubygems/blob/v3.5.9/lib/rubygems/deprecate.rb - # - # Pass in the deprecated method name, the new method name, and the year & month it'll be removed +module Bridgetown::Foundation + # This is loosly based on the `deprecate` method in `Gem::Deprecate` # + # @param target [Object] # @param name [Symbol] e.g. `:howdy` # @param repl [Symbol] e.g. `:hello` # @param year [Integer] e.g. `2025` # @param month [Integer] e.g. `1` for January - def gem_deprecate(name, repl, year, month) - # rubocop:disable Style/FormatStringToken - class_eval do - old = "_deprecated_#{name}" - alias_method old, name - define_method name do |*args, &block| - klass = is_a? Module - target = klass ? "#{self}." : "#{self.class}#" - msg = [ - "NOTE: #{target}#{name} is deprecated", - repl == :none ? " with no replacement" : "; use #{repl} instead", - format(". It will be removed on or after %4d-%02d.", year, month), - "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", - ] - warn "#{msg.join}." unless Gem::Deprecate.skip - send old, *args, &block - end - end - # rubocop:enable Style/FormatStringToken + def self.deprecation_warning(target, name, repl, year, month) # rubocop:disable Metrics/ParameterLists + klass = target.is_a?(Module) + target = klass ? "#{self}." : "#{self.class}#" + msg = [ + "NOTE: #{target}#{name} is deprecated", + repl == :none ? " with no replacement" : "; use #{repl} instead", + format(". It will be removed on or after %4d-%02d.", year, month), # rubocop:disable Style/FormatStringToken + "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", + ] + warn "#{msg.join}." end end +# You can add `using Bridgetown::Refinements` to any portion of your Ruby code to load in all +# of the refinements available in Foundation. Or you can add a using statement for a particular +# refinement which lives inside `Bridgetown::Foundation::RefineExt`. +module Bridgetown::Refinements +end + Zeitwerk.with_loader do |l| l.push_dir "#{__dir__}/bridgetown/foundation", namespace: Bridgetown::Foundation l.ignore "#{__dir__}/bridgetown/foundation/version.rb" diff --git a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb b/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb index 71e6a53e8..74f31ce58 100644 --- a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb +++ b/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb @@ -3,30 +3,6 @@ module Bridgetown::Foundation module CoreExt module String - module Indentation - def indent!(indent_by, *args) - if args.length.positive? - Kernel.warn "multiple arguments aren't supported by `indent!' in Bridgetown", uplevel: 1 - end - - gsub! %r!^(?\!$)!, " " * indent_by - end - - def indent(indent_by, *args) - if args.length.positive? - Kernel.warn "multiple arguments aren't supported by `indent' in Bridgetown", uplevel: 1 - end - - dup.indent!(indent_by) - end - end - - module Questionable - def questionable = Bridgetown::Foundation::QuestionableString.new(self) - alias_method :inquiry, :questionable - gem_deprecate :inquiry, :questionable, 2024, 12 - end - module StartsWithAndEndsWith def self.included(klass) klass.alias_method :starts_with?, :start_with? @@ -34,7 +10,7 @@ def self.included(klass) end end - ::String.include Indentation, Questionable, StartsWithAndEndsWith + ::String.include StartsWithAndEndsWith end end end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/module.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/module.rb similarity index 68% rename from bridgetown-foundation/lib/bridgetown/foundation/core_ext/module.rb rename to bridgetown-foundation/lib/bridgetown/foundation/refine_ext/module.rb index 76f025b8b..d432b2401 100644 --- a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/module.rb +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/module.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true module Bridgetown::Foundation - module CoreExt + module RefineExt module Module - module Nested + using RefineExt::Object + + refine ::Module do def nested_within?(other) - other.nested_parents.within?(nested_parents[1..]) + return false if self == other + + other.nested_parents.within?(nested_parents) #[1..]) end def nested_parents @@ -25,8 +29,12 @@ def nested_name name&.split("::")&.last end end - - ::Module.include Nested end end end + +module Bridgetown + module Refinements + include Foundation::RefineExt::Module + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/object.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/object.rb similarity index 87% rename from bridgetown-foundation/lib/bridgetown/foundation/core_ext/object.rb rename to bridgetown-foundation/lib/bridgetown/foundation/refine_ext/object.rb index d1c0bf448..ac54c889f 100644 --- a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/object.rb +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/object.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module Bridgetown::Foundation - module CoreExt + module RefineExt module Object - module WithinOther + refine ::Object do # This method lets you check if the receiver is "within" the other object. In most cases, # this check is accomplished via the `include?` method…aka, `10.within? [5, 10]` would # return `true` as `[5, 10].include? 10` is true. And String/String comparison are @@ -32,6 +32,8 @@ def within?(other) # rubocop:disable Metrics end if is_a?(Array) && other.is_a?(Array) + return false if empty? + return difference(other).empty? end @@ -51,11 +53,16 @@ def within?(other) # rubocop:disable Metrics # NOTE: if you _really_ need to preserve Active Support's `in?` functionality, you can just # require "active_support/core_ext/object/inclusion" - alias_method :in?, :within? - gem_deprecate :in?, :within?, 2024, 12 + def in?(...) = Bridgetown::Foundation.deprecation_warning( + self, :in?, :within?, 2024, 12 + ).then { within?(...) } end - - ::Object.include WithinOther end end end + +module Bridgetown + module Refinements + include Foundation::RefineExt::Object + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/string.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/string.rb new file mode 100644 index 000000000..bf9450b4d --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/string.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module RefineExt + module String + refine ::String do + def indent!(indent_by, *args) + if args.length.positive? + Kernel.warn "multiple arguments aren't supported by `indent!' in Bridgetown", uplevel: 1 + end + + gsub! %r!^(?\!$)!, " " * indent_by + end + + def indent(indent_by, *args) + if args.length.positive? + Kernel.warn "multiple arguments aren't supported by `indent' in Bridgetown", uplevel: 1 + end + + dup.indent!(indent_by) + end + + def questionable = Bridgetown::Foundation::QuestionableString.new(self) + + def inquiry = Bridgetown::Foundation.deprecation_warning( + self, :inquiry, :questionable, 2024, 12 + ).then { questionable } + end + end + end +end + +module Bridgetown + module Refinements + include Foundation::RefineExt::String + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/symbol.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/symbol.rb new file mode 100644 index 000000000..e2a65fc0d --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/symbol.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module RefineExt + module Symbol + refine ::Symbol do + def with(...) + ->(caller, *rest) { caller.public_send(self, *rest, ...) } + end + + def call(...) + ->(caller, *rest) { caller.public_send(self, *rest).public_send(...) } + end + end + end + end +end + +module Bridgetown + module Refinements + include Foundation::RefineExt::Symbol + end +end diff --git a/bridgetown-foundation/test/test_string.rb b/bridgetown-foundation/test/test_string.rb index 5597e644b..4b126dccc 100644 --- a/bridgetown-foundation/test/test_string.rb +++ b/bridgetown-foundation/test/test_string.rb @@ -3,6 +3,8 @@ require "test_helper" class TestString < Minitest::Test + using Bridgetown::Refinements + def test_that_it_has_a_version_number refute_nil ::Bridgetown::Foundation::VERSION end @@ -31,4 +33,10 @@ def test_starts_ends_with assert "this".ends_with?("is") refute "this".ends_with?("si") end + + # TODO: more testing of other data types + def test_within + assert "abc".within? ["def", "abc"] + refute "abc".within? ["def"] + end end