diff --git a/Gemfile b/Gemfile index f434b8936..fccdd6ec9 100644 --- a/Gemfile +++ b/Gemfile @@ -29,4 +29,5 @@ end gem "bridgetown", path: "bridgetown" gem "bridgetown-builder", path: "bridgetown-builder" gem "bridgetown-core", path: "bridgetown-core" +gem "bridgetown-foundation", path: "bridgetown-foundation" gem "bridgetown-paginate", path: "bridgetown-paginate" diff --git a/Gemfile.lock b/Gemfile.lock index 5abb642ef..f96c7c029 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,16 +8,15 @@ PATH remote: bridgetown-core specs: bridgetown-core (1.3.4) - activemodel (>= 6.0, < 8.0) activesupport (>= 6.0, < 8.0) addressable (~> 2.4) amazing_print (~> 1.2) - colorator (~> 1.0) + bridgetown-foundation (= 1.3.4) csv (~> 3.2) + dry-inflector (>= 1.0) erubi (~> 1.9) faraday (~> 2.0) faraday-follow_redirects (~> 0.3) - hash_with_dot_access (~> 1.2) i18n (~> 1.0) kramdown (~> 2.1) kramdown-parser-gfm (~> 1.0) @@ -35,6 +34,14 @@ PATH tilt (~> 2.0) zeitwerk (~> 2.5) +PATH + remote: bridgetown-foundation + specs: + bridgetown-foundation (1.3.4) + hash_with_dot_access (~> 2.0) + inclusive (~> 1.0) + zeitwerk (~> 2.5) + PATH remote: bridgetown-paginate specs: @@ -52,8 +59,6 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (7.1.3.2) - activesupport (= 7.1.3.2) activesupport (7.1.3.2) base64 bigdecimal @@ -75,13 +80,13 @@ GEM benchmark-ips (2.13.0) bigdecimal (3.1.7) builder (3.2.4) - colorator (1.1.0) concurrent-ruby (1.2.3) connection_pool (2.4.1) csv (3.3.0) diff-lcs (1.5.1) docile (1.4.0) drb (2.2.1) + dry-inflector (1.0.0) e2mmap (0.1.0) erubi (1.12.0) faraday (2.9.0) @@ -91,10 +96,10 @@ GEM faraday-net_http (3.1.0) net-http ffi (1.16.3) - hash_with_dot_access (1.2.0) - activesupport (>= 5.0.0, < 8.0) + hash_with_dot_access (2.1.1) i18n (1.14.4) concurrent-ruby (~> 1.0) + inclusive (1.0.0) jaro_winkler (1.5.6) json (2.7.2) kramdown (2.4.0) @@ -107,7 +112,7 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) memory_profiler (1.0.1) - mini_portile2 (2.8.5) + mini_portile2 (2.8.6) minitest (5.22.3) minitest-profile (0.0.2) minitest-reporters (1.6.1) @@ -153,14 +158,14 @@ GEM reverse_markdown (2.1.1) nokogiri rexml (3.2.6) - roda (3.78.0) + roda (3.79.0) rack rouge (4.2.1) rspec-mocks (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) - rubocop (1.63.1) + rubocop (1.63.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -239,6 +244,7 @@ DEPENDENCIES bridgetown! bridgetown-builder! bridgetown-core! + bridgetown-foundation! bridgetown-paginate! memory_profiler minitest diff --git a/Rakefile b/Rakefile index dff6fccce..aeaa932bd 100644 --- a/Rakefile +++ b/Rakefile @@ -5,6 +5,7 @@ task default: %w(test_all) desc "Test all Bridgetown gems in monorepo" task :test_all do sh "cd bridgetown-core && script/cibuild" + sh "cd bridgetown-foundation && script/cibuild" sh "cd bridgetown-builder && script/cibuild" sh "cd bridgetown-paginate && script/cibuild" sh "cd bridgetown-routes && script/cibuild" @@ -12,6 +13,7 @@ end task :release_all_unsafe do sh "cd bridgetown-core && bundle exec rake release" + sh "cd bridgetown-foundation && bundle exec rake release" sh "cd bridgetown-builder && bundle exec rake release" sh "cd bridgetown-paginate && bundle exec rake release" sh "cd bridgetown-routes && bundle exec rake release" diff --git a/bridgetown-builder/lib/bridgetown-builder/builder.rb b/bridgetown-builder/lib/bridgetown-builder/builder.rb index 7ee1a38d7..6519a9172 100644 --- a/bridgetown-builder/lib/bridgetown-builder/builder.rb +++ b/bridgetown-builder/lib/bridgetown-builder/builder.rb @@ -3,31 +3,33 @@ module Bridgetown # Superclass for a website's SiteBuilder abstract class class Builder < Bridgetown::Builders::PluginBuilder - extend ActiveSupport::DescendantsTracker - include ActiveSupport::Callbacks - - define_callbacks :build - class << self def register Bridgetown::Builders::PluginBuilder.plugin_registrations << self end def before_build(...) - set_callback(:build, :before, ...) + add_callback(:before, ...) end def after_build(...) - set_callback(:build, :after, ...) + add_callback(:after, ...) + end + + def callbacks + @callbacks ||= {} end - def around_build(...) - set_callback(:build, :around, ...) + def add_callback(name, method_name = nil, &block) + callbacks[name] ||= [] + callbacks[name] << (block || proc { send(method_name) }) end end def build_with_callbacks - run_callbacks(:build) { build } + self.class.callbacks[:before]&.each { instance_exec(&_1) } + build + self.class.callbacks[:after]&.each { instance_exec(&_1) } self end diff --git a/bridgetown-builder/test/test_hooks.rb b/bridgetown-builder/test/test_hooks.rb index 253086e21..299b4507a 100644 --- a/bridgetown-builder/test/test_hooks.rb +++ b/bridgetown-builder/test/test_hooks.rb @@ -18,9 +18,23 @@ class SiteBuilder < Builder end class SubclassOfSiteBuilder < SiteBuilder + class << self + attr_accessor :final_value + end + + after_build :run_after + def build site.config[:site_builder_subclass_loaded] = true end + + def run_after + self.class.final_value = [@initial_value, :goodbye] + end +end + +SubclassOfSiteBuilder.before_build do + @initial_value = :hello end class TestHooks < BridgetownUnitTest @@ -50,9 +64,11 @@ class TestHooks < BridgetownUnitTest @site.reset @site.loaders_manager.unload_loaders @site.setup + SubclassOfSiteBuilder.final_value = nil Bridgetown::Hooks.trigger :site, :pre_read, @site assert @site.config[:site_builder_subclass_loaded] + assert_equal [:hello, :goodbye], SiteBuilder.subclasses.first.final_value end end end diff --git a/bridgetown-core/benchmark/dot-access.rb b/bridgetown-core/benchmark/dot-access.rb new file mode 100755 index 000000000..4b8b6ea9a --- /dev/null +++ b/bridgetown-core/benchmark/dot-access.rb @@ -0,0 +1,108 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler" +Bundler.setup +require "benchmark/ips" +require "active_support/core_ext/hash/indifferent_access" +require "hash_with_dot_access" + +class User < HashWithDotAccess::Hash +end + +user = User.new({ address: { category: { desc: "Urban" } } }) + +using HashWithDotAccess::Refinements + +# Enable and start GC before each job run. Disable GC afterwards. +# +# Inspired by https://www.omniref.com/ruby/2.2.1/symbols/Benchmark/bm?#annotation=4095926&line=182 +class GCSuite + def warming(*) + run_gc + end + + def running(*) + run_gc + end + + def warmup_stats(*); end + + def add_report(*); end + + private + + def run_gc + GC.enable + GC.start + GC.disable + end +end + +suite = GCSuite.new + +Benchmark.ips do |x| + x.config(suite:, time: 1, warmup: 1) + + x.report("standard hash") do + h = { "foo" => "bar" } + h["foo"] + end + x.report("standard hash with fetch") do + h = { "foo" => "bar" } + h.fetch("foo", nil) + end + x.report("standard hash - symbol keys") do + h = { foo: "bar" } + h[:foo] + end + x.report("standard hash with fetch - symbol keys") do + h = { foo: "bar" } + h.fetch(:foo, nil) + end + x.report("hash with indifferent access string") do + h = { "foo" => "bar" }.with_indifferent_access + h["foo"] + end + x.report("hash with indifferent access symbol") do + h = { "foo" => "bar" }.with_indifferent_access + h[:foo] + end + x.report("hash with indifferent access via new method") do + h = ActiveSupport::HashWithIndifferentAccess.new({ "foo" => "bar" }) + h[:foo] + end + # x.report("hash with indifferent access via []") do + # h = ActiveSupport::HashWithIndifferentAccess[{ "foo" => "bar" }] + # h[:foo] + # end + x.report("hash as_dots and symbol access") do + h = { foo: "bar" }.as_dots + h[:foo] + end + x.report("hash as_dots and method access") do + h = { foo: "bar" }.as_dots + h.foo + end + x.report("hash with dot access new method, string init, and symbol access") do + h = HashWithDotAccess::Hash.new({ "foo" => "bar" }) + h[:foo] + end + x.report("hash with dot access new method, symbol init, and method access") do + h = HashWithDotAccess::Hash.new(foo: "bar") + h.foo + end + x.report("hash with dot access new method, string access") do + h = HashWithDotAccess::Hash.new({ "foo" => "bar" }) + h["foo"] + end + user = { address: { category: { desc: "Urban" } } } + x.report("nested symbols") do + user[:address][:category][:desc] + end + userd = User.new({ address: { category: { desc: "Urban" } } }) + x.report("nested dots") do + userd.address.category.desc + end + x.compare! +end diff --git a/bridgetown-core/benchmark/indifferent-access.rb b/bridgetown-core/benchmark/indifferent-access.rb deleted file mode 100755 index 28cc6a35a..000000000 --- a/bridgetown-core/benchmark/indifferent-access.rb +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require "benchmark/ips" -require "active_support/core_ext/hash/indifferent_access" -require "hash_with_dot_access" - -Benchmark.ips do |x| - x.config(time: 1, warmup: 1) - - x.report("standard hash") do - h = { "foo" => "bar" } - h["foo"] - end - x.report("hash with indifferent access string") do - h = { "foo" => "bar" }.with_indifferent_access - h["foo"] - end - x.report("hash with indifferent access symbol") do - h = { "foo" => "bar" }.with_indifferent_access - h[:foo] - end - x.report("hash with indifferent access via new method") do - h = ActiveSupport::HashWithIndifferentAccess.new({ "foo" => "bar" }) - h[:foo] - end - x.report("hash with indifferent access via []") do - h = ActiveSupport::HashWithIndifferentAccess[{ "foo" => "bar" }] - h[:foo] - end - x.report("hash with_dot_access") do - h = { "foo" => "bar" }.with_dot_access - h[:foo] - end - x.report("hash with dot access via new method") do - h = HashWithDotAccess::Hash.new({ "foo" => "bar" }) - h[:foo] - end - x.report("hash with dot access via []") do - h = HashWithDotAccess::Hash[{ "foo" => "bar" }] - h[:foo] - end - x.report("hash with dot access via [] and using a method") do - h = HashWithDotAccess::Hash[{ "foo" => "bar" }] - h.foo - end - x.report("hash with dot access via new and using a method") do - h = HashWithDotAccess::Hash.new({ "foo" => "bar" }) - h.foo - end - x.report("hash with dot access via new and using string keys") do - h = HashWithDotAccess::Hash.new({ "foo" => "bar" }) - h["foo"] - end - x.compare! -end diff --git a/bridgetown-core/bin/bridgetown b/bridgetown-core/bin/bridgetown index d6b9d5bc0..7ebba7f39 100755 --- a/bridgetown-core/bin/bridgetown +++ b/bridgetown-core/bin/bridgetown @@ -5,12 +5,6 @@ STDOUT.sync = true $LOAD_PATH.unshift File.expand_path("../../bridgetown/lib", __dir__) -# Used by commands/automations -require "active_support" -require "active_support/core_ext/array/extract_options" -require "active_support/core_ext/string/strip" -require "active_support/core_ext/string/indent" - require "bridgetown" # Support NO_COLOR: https://no-color.org diff --git a/bridgetown-core/bridgetown-core.gemspec b/bridgetown-core/bridgetown-core.gemspec index c6757b529..e26b04f81 100644 --- a/bridgetown-core/bridgetown-core.gemspec +++ b/bridgetown-core/bridgetown-core.gemspec @@ -31,16 +31,15 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 3.1.0" - s.add_runtime_dependency("activemodel", [">= 6.0", "< 8.0"]) s.add_runtime_dependency("activesupport", [">= 6.0", "< 8.0"]) s.add_runtime_dependency("addressable", "~> 2.4") s.add_runtime_dependency("amazing_print", "~> 1.2") - s.add_runtime_dependency("colorator", "~> 1.0") + s.add_runtime_dependency("bridgetown-foundation", Bridgetown::VERSION) s.add_runtime_dependency("csv", "~> 3.2") + s.add_runtime_dependency("dry-inflector", ">= 1.0") s.add_runtime_dependency("erubi", "~> 1.9") s.add_runtime_dependency("faraday", "~> 2.0") s.add_runtime_dependency("faraday-follow_redirects", "~> 0.3") - s.add_runtime_dependency("hash_with_dot_access", "~> 1.2") s.add_runtime_dependency("i18n", "~> 1.0") s.add_runtime_dependency("kramdown", "~> 2.1") s.add_runtime_dependency("kramdown-parser-gfm", "~> 1.0") diff --git a/bridgetown-core/lib/bridgetown-core.rb b/bridgetown-core/lib/bridgetown-core.rb index 9dde66dc7..fd62d5eb7 100644 --- a/bridgetown-core/lib/bridgetown-core.rb +++ b/bridgetown-core/lib/bridgetown-core.rb @@ -31,31 +31,23 @@ def require_all(path) require "json" require "yaml" +# Pull in Foundation gem +require "bridgetown-foundation" + # 3rd party -require "active_support" -require "active_support/core_ext/class/attribute" -require "active_support/core_ext/hash/keys" -require "active_support/core_ext/module/delegation" +require "active_support" # TODO: remove by the end of 2024 require "active_support/core_ext/object/blank" -require "active_support/core_ext/object/deep_dup" -require "active_support/core_ext/object/inclusion" require "active_support/core_ext/string/inflections" -require "active_support/core_ext/string/inquiry" require "active_support/core_ext/string/output_safety" -require "active_support/core_ext/string/starts_ends_with" -require "active_support/current_attributes" -require "hash_with_dot_access" require "addressable/uri" require "liquid" require "listen" require "kramdown" -require "colorator" require "i18n" require "i18n/backend/fallbacks" require "faraday" require "signalize" require "thor" -require "zeitwerk" # Ensure we can set up fallbacks so the default locale gets used I18n::Backend::Simple.include I18n::Backend::Fallbacks @@ -74,6 +66,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" @@ -87,6 +81,7 @@ module Bridgetown autoload :FrontMatter, "bridgetown-core/front_matter" autoload :GeneratedPage, "bridgetown-core/generated_page" autoload :Hooks, "bridgetown-core/hooks" + autoload :Inflector, "bridgetown-core/inflector" autoload :Layout, "bridgetown-core/layout" autoload :LayoutPlaceable, "bridgetown-core/concerns/layout_placeable" autoload :LayoutReader, "bridgetown-core/readers/layout_reader" @@ -132,7 +127,7 @@ class << self # Tells you which Bridgetown environment you are building in so # you can skip tasks if you need to. def environment - (ENV["BRIDGETOWN_ENV"] || "development").inquiry + (ENV["BRIDGETOWN_ENV"] || "development").questionable end alias_method :env, :environment @@ -380,7 +375,6 @@ def build_errors_path end end - module CoreExt; end module Model; end module Resource @@ -402,15 +396,9 @@ def self.===(other) end end -Zeitwerk::Loader.new.tap do |loader| - loader.push_dir File.join(__dir__, "bridgetown-core/core_ext"), namespace: Bridgetown::CoreExt - loader.setup - loader.eager_load -end - -Zeitwerk::Loader.new.tap do |loader| - loader.push_dir File.join(__dir__, "bridgetown-core/model"), namespace: Bridgetown::Model - loader.push_dir File.join(__dir__, "bridgetown-core/resource"), namespace: Bridgetown::Resource - loader.setup # ready! +Zeitwerk.with_loader do |l| + l.push_dir File.join(__dir__, "bridgetown-core/model"), namespace: Bridgetown::Model + l.push_dir File.join(__dir__, "bridgetown-core/resource"), namespace: Bridgetown::Resource + l.setup # ready! end Bridgetown::Model::Origin # this needs to load first diff --git a/bridgetown-core/lib/bridgetown-core/collection.rb b/bridgetown-core/lib/bridgetown-core/collection.rb index 1132a6bf8..783431d3f 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] @@ -22,7 +23,7 @@ def initialize(site, label) end def builtin? - @is_builtin ||= label.in?(%w(posts pages data).freeze) + @is_builtin ||= label.within?(%w(posts pages data).freeze) end def data? @@ -54,6 +55,8 @@ def resources_by_relative_url # Iterate over Resources, support Enumerable def each(...) = resources.each(...) + def deconstruct = resources.deconstruct + # Fetch the static files in this collection. # Defaults to an empty array if no static files have been read in. # @@ -247,7 +250,7 @@ def merge_data_resources # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticCompl end end - merge_environment_specific_metadata(data_contents).with_dot_access + merge_environment_specific_metadata(data_contents).as_dots end def merge_environment_specific_metadata(data_contents) 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..2b2da83a7 100644 --- a/bridgetown-core/lib/bridgetown-core/commands/console.rb +++ b/bridgetown-core/lib/bridgetown-core/commands/console.rb @@ -95,6 +95,9 @@ def console # rubocop:disable Metrics workspace = IRB::WorkSpace.new workspace.main.define_singleton_method(:site) { Bridgetown::Current.site } workspace.main.define_singleton_method(:collections) { site.collections } + workspace.main.define_singleton_method(:helpers) do + Bridgetown::RubyTemplateView::Helpers.new + end irb = IRB::Irb.new(workspace) IRB.conf[:IRB_RC]&.call(irb.context) IRB.conf[:MAIN_CONTEXT] = irb.context diff --git a/bridgetown-core/lib/bridgetown-core/commands/esbuild.rb b/bridgetown-core/lib/bridgetown-core/commands/esbuild.rb index 579bacf29..f47b01e57 100644 --- a/bridgetown-core/lib/bridgetown-core/commands/esbuild.rb +++ b/bridgetown-core/lib/bridgetown-core/commands/esbuild.rb @@ -24,7 +24,7 @@ def esbuild return show_actions if args.empty? action = args.first - if supported_actions.include?(action) + if supported_actions.include?(action.to_sym) perform action else @logger.error "Error:".red, "🚨 Please enter a valid action." @@ -66,7 +66,7 @@ def show_actions longest_action = supported_actions.keys.max_by(&:size).size supported_actions.each do |action, description| - say "#{action.ljust(longest_action).to_s.bold.blue}\t# #{description}" + say "#{action.to_s.ljust(longest_action).bold.blue}\t# #{description}" end end @@ -76,7 +76,7 @@ def supported_actions update: "Updates the Bridgetown esbuild defaults to the latest available version", "migrate-from-webpack": "Removes Webpack from your project and installs/configures esbuild", - }.with_indifferent_access + } end end end diff --git a/bridgetown-core/lib/bridgetown-core/commands/start.rb b/bridgetown-core/lib/bridgetown-core/commands/start.rb index dca2a7590..960c111eb 100644 --- a/bridgetown-core/lib/bridgetown-core/commands/start.rb +++ b/bridgetown-core/lib/bridgetown-core/commands/start.rb @@ -32,7 +32,7 @@ class Start < Thor::Group extend BuildOptions extend Summarizable include ConfigurationOverridable - include Bridgetown::Utils::PidTracker + include Inclusive Registrations.register do register(Start, "start", "start", Start.summary) @@ -62,6 +62,7 @@ def self.banner summary "Start the web server, frontend bundler, and Bridgetown watcher" def start + pid_tracker = packages[Bridgetown::Foundation::Packages::PidTracker] Bridgetown.logger.writer.enable_prefix Bridgetown::Commands::Build.print_startup_message sleep 0.25 @@ -84,19 +85,19 @@ def start config: "config.ru", }).tap do |server| if server.serveable? - create_pid_dir + pid_tracker.create_pid_dir bt_options.skip_live_reload = !server.using_puma? build_args = ["-w"] + ARGV.reject { |arg| arg == "start" } build_pid = Process.fork { Bridgetown::Commands::Build.start(build_args) } - add_pid(build_pid, file: :bridgetown) + pid_tracker.add_pid(build_pid, file: :bridgetown) after_stop_callback = -> { say "Stopping Bridgetown server..." Bridgetown::Hooks.trigger :site, :server_shutdown Process.kill "SIGINT", build_pid - remove_pidfile :bridgetown + pid_tracker.remove_pidfile :bridgetown # Shut down the frontend bundler etc. if they're running unless Bridgetown.env.production? || bt_options[:skip_frontend] diff --git a/bridgetown-core/lib/bridgetown-core/component.rb b/bridgetown-core/lib/bridgetown-core/component.rb index 4dc4f2eb2..a7ef15365 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 @@ -228,9 +229,7 @@ def _renderer end def helpers - @helpers ||= Bridgetown::RubyTemplateView::Helpers.new( - self, view_context&.site || Bridgetown::Current.site - ) + @helpers ||= Bridgetown::RubyTemplateView::Helpers.new(self, view_context&.site) end def method_missing(method, ...) diff --git a/bridgetown-core/lib/bridgetown-core/concerns/prioritizable.rb b/bridgetown-core/lib/bridgetown-core/concerns/prioritizable.rb index e2f71107f..a08169391 100644 --- a/bridgetown-core/lib/bridgetown-core/concerns/prioritizable.rb +++ b/bridgetown-core/lib/bridgetown-core/concerns/prioritizable.rb @@ -26,11 +26,21 @@ def priority(priority = nil) def <=>(other) priorities[other.priority] <=> priorities[priority] end + + # Return either this class' priorities or the superclass which has them (if any) + def priorities + return @priorities if @priorities + + superclass.priorities if superclass.respond_to?(:priorities) + end + + def priorities=(val) + @priorities = val + end end def self.included(klass) klass.extend ClassMethods - klass.class_attribute :priorities, instance_accessor: false end # Spaceship is priority [higher -> lower] diff --git a/bridgetown-core/lib/bridgetown-core/concerns/site/content.rb b/bridgetown-core/lib/bridgetown-core/concerns/site/content.rb index d5ee5222b..480c861dd 100644 --- a/bridgetown-core/lib/bridgetown-core/concerns/site/content.rb +++ b/bridgetown-core/lib/bridgetown-core/concerns/site/content.rb @@ -3,6 +3,8 @@ class Bridgetown::Site # Content is king! module Content + using Bridgetown::Refinements + def resources_grouped_by_taxonomy(taxonomy) data.site_taxonomies_hash ||= {} data.site_taxonomies_hash[taxonomy.label] ||= taxonomy.terms.transform_values do |terms| @@ -45,10 +47,10 @@ def site_payload # # If `config["collections"]` is not specified, a blank hash is returned. # - # @return [Hash{String, Symbol => Collection}] A Hash + # @return [HashWithDotAccess::Hash{String, Symbol => Collection}] A Hash # containing a collection name-to-instance pairs. # - # @return [Hash] Returns a blank hash if no items found + # @return [HashWithDotAccess::Hash] Returns a blank hash if no items found def collections @collections ||= collection_names.each_with_object( HashWithDotAccess::Hash.new @@ -66,7 +68,7 @@ def collection_names # @return [Array] def taxonomy_types - @taxonomy_types ||= config.taxonomies.to_h do |label, key_or_metadata| + @taxonomy_types ||= config.taxonomies.to_dot_h do |label, key_or_metadata| key = key_or_metadata tax_metadata = if key_or_metadata.is_a? Hash key = key_or_metadata["key"] @@ -78,7 +80,7 @@ def taxonomy_types [label, Bridgetown::Resource::TaxonomyType.new( site: self, label:, key:, metadata: tax_metadata ),] - end.with_dot_access + end end # Get all loaded resources. 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..921551779 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) diff --git a/bridgetown-core/lib/bridgetown-core/configuration.rb b/bridgetown-core/lib/bridgetown-core/configuration.rb index acfc1c9d0..f809af6ef 100644 --- a/bridgetown-core/lib/bridgetown-core/configuration.rb +++ b/bridgetown-core/lib/bridgetown-core/configuration.rb @@ -3,6 +3,8 @@ module Bridgetown # The primary configuration object for a Bridgetown project class Configuration < HashWithDotAccess::Hash + using Bridgetown::Refinements + REQUIRE_DENYLIST = %i(parse_routes ssr) # rubocop:disable Style/MutableConstant Initializer = Struct.new(:name, :block, :completed, keyword_init: true) do @@ -41,7 +43,7 @@ def initialize(*) category: { key: "categories", title: "Category" }, tag: { key: "tags", title: "Tag" }, }, "autoload_paths" => [], - "inflector" => nil, + "inflector" => Bridgetown::Inflector.new, "eager_load_paths" => [], "autoloader_collapsed_paths" => [], "additional_watch_paths" => [], diff --git a/bridgetown-core/lib/bridgetown-core/configuration/configuration_dsl.rb b/bridgetown-core/lib/bridgetown-core/configuration/configuration_dsl.rb index 869c9b5f2..2f5999cd2 100644 --- a/bridgetown-core/lib/bridgetown-core/configuration/configuration_dsl.rb +++ b/bridgetown-core/lib/bridgetown-core/configuration/configuration_dsl.rb @@ -27,7 +27,9 @@ def init(name, require_gem: true, require_initializer: true, **kwargs, &block) # end Bridgetown.logger.debug "Initializing:", name - @scope.initializers[name.to_sym].block.(self, **@scope.init_params[name].symbolize_keys) + @scope.initializers[name.to_sym].block.( + self, **@scope.init_params[name].transform_keys(&:to_sym) + ) initializer.completed = true end diff --git a/bridgetown-core/lib/bridgetown-core/current.rb b/bridgetown-core/lib/bridgetown-core/current.rb index 78e93363f..7a8bc7ed6 100644 --- a/bridgetown-core/lib/bridgetown-core/current.rb +++ b/bridgetown-core/lib/bridgetown-core/current.rb @@ -1,28 +1,30 @@ # frozen_string_literal: true +Thread.attr_accessor :bridgetown_state + module Bridgetown - class Current < ActiveSupport::CurrentAttributes - # @!method self.preloaded_configuration - # @return [Bridgetown::Configuration] - attribute :preloaded_configuration + class Current + class << self + def thread_state = Thread.current.bridgetown_state ||= {} - # @return [Bridgetown::Site, nil] - def self.site - sites[:main] - end + # @return [Bridgetown::Site, nil] + def site = sites[:main] - def self.site=(new_site) - sites[:main] = new_site - end + def site=(new_site) + sites[:main] = new_site + end - # @!method self.sites - # @return [Hash] + # @return [Hash] + def sites + thread_state[:sites] ||= {} + end - attribute :sites + # @return [Bridgetown::Configuration] + def preloaded_configuration = thread_state[:preloaded_configuration] - def initialize - super - @attributes[:sites] = {} + def preloaded_configuration=(value) + thread_state[:preloaded_configuration] = value + end end end end diff --git a/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb b/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb index 2fdb6952d..de0399e5a 100644 --- a/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb +++ b/bridgetown-core/lib/bridgetown-core/drops/relations_drop.rb @@ -3,16 +3,17 @@ module Bridgetown module Drops class RelationsDrop < Drop + using Bridgetown::Refinements mutable false def [](type) - return nil unless type.to_s.in?(@obj.relation_types) + return nil unless type.to_s.within?(@obj.relation_types) @obj.resources_for_type(type) end def key?(type) - type.to_s.in?(@obj.relation_types) + type.to_s.within?(@obj.relation_types) end def fallback_data diff --git a/bridgetown-core/lib/bridgetown-core/filters/from_liquid.rb b/bridgetown-core/lib/bridgetown-core/filters/from_liquid.rb index 39ceaddca..f8a9870b8 100644 --- a/bridgetown-core/lib/bridgetown-core/filters/from_liquid.rb +++ b/bridgetown-core/lib/bridgetown-core/filters/from_liquid.rb @@ -5,19 +5,15 @@ module Filters module FromLiquid extend Liquid::StandardFilters - def strip_html(input) - FromLiquid.strip_html(input) - end + def strip_html(...) = FromLiquid.strip_html(...) - def strip_newlines(input) - FromLiquid.strip_newlines(input) - end + def strip_newlines(...) = FromLiquid.strip_newlines(...) - def newline_to_br(input) - FromLiquid.newline_to_br(input) - end + def newline_to_br(...) = FromLiquid.newline_to_br(...) - # FYI, truncate and truncate words are already provided by ActiveSupport! =) + def truncate(...) = FromLiquid.truncate(...) + + def truncate_words(...) = FromLiquid.truncatewords(...) end end end diff --git a/bridgetown-core/lib/bridgetown-core/filters/translation_filters.rb b/bridgetown-core/lib/bridgetown-core/filters/translation_filters.rb index e5b4cc6bc..686b552ad 100644 --- a/bridgetown-core/lib/bridgetown-core/filters/translation_filters.rb +++ b/bridgetown-core/lib/bridgetown-core/filters/translation_filters.rb @@ -17,7 +17,7 @@ def t(input, options = "") private def string_to_hash(options) - options.split(",").to_h { |e| e.split(":").map(&:strip) }.symbolize_keys + options.split(",").to_h { |e| e.split(":").map(&:strip) }.transform_keys(&:to_sym) end end end diff --git a/bridgetown-core/lib/bridgetown-core/helpers.rb b/bridgetown-core/lib/bridgetown-core/helpers.rb index 9f181dad8..1a484ca90 100644 --- a/bridgetown-core/lib/bridgetown-core/helpers.rb +++ b/bridgetown-core/lib/bridgetown-core/helpers.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true require "streamlined/helpers" -require "active_support/html_safe_translation" module Bridgetown class RubyTemplateView class Helpers + using Bridgetown::Refinements include Bridgetown::Filters include Bridgetown::Filters::FromLiquid include ::Streamlined::Helpers + include Inclusive # @return [Bridgetown::RubyTemplateView] attr_reader :view @@ -18,11 +19,14 @@ class Helpers Context = Struct.new(:registers) + # @return [Bridgetown::Foundation::SafeTranslations] + packages def translate_package = [Bridgetown::Foundation::Packages::SafeTranslations] + # @param view [Bridgetown::RubyTemplateView] # @param site [Bridgetown::Site] - def initialize(view, site) + def initialize(view = nil, site = nil) @view = view - @site = site + @site = site || Bridgetown::Current.site # duck typing for Liquid context @context = Context.new({ site: }) @@ -155,10 +159,17 @@ def translate(key, **options) # rubocop:disable Metrics/CyclomaticComplexity, Me key = "#{view_path.tr("/", ".")}#{key}" if view_path.present? end - ActiveSupport::HtmlSafeTranslation.translate(key, **options) + return I18n.translate(key, **options) unless %r{(?:_|\b)html\z}.match?(key) + + translate_with_html(key, **options) end alias_method :t, :translate + def translate_with_html(key, **options) + escaper = ->(input) { input.to_s.encode(xml: :attr).gsub(%r{\A"|"\Z}, "") } + translate_package.translate(key, escaper, **options) + end + # Delegates to I18n.localize with no additional functionality. # # @return [String] the localized string diff --git a/bridgetown-core/lib/bridgetown-core/inflector.rb b/bridgetown-core/lib/bridgetown-core/inflector.rb new file mode 100644 index 000000000..5b889c53a --- /dev/null +++ b/bridgetown-core/lib/bridgetown-core/inflector.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "dry/inflector" + +module Bridgetown + class Inflector < Dry::Inflector + def initialize(&) # rubocop:disable Lint/MissingSuper + @inflections = Dry::Inflector::Inflections.build do |inflections| + inflections.acronym "HTML" + inflections.acronym "CSS" + inflections.acronym "JS" + end + configure(&) if block_given? + end + + def configure + yield inflections + end + + # for compatibility with Zeitwerk + def camelize(basename, *) + super(basename) + end + + def to_s + "#" + end + alias_method :inspect, :to_s + + def ==(other) + return super unless other.is_a?(Bridgetown::Inflector) + + # NOTE: strictly speaking, this might be wrong if two inflector instances have different + # rule sets…but as this equality check is mainly done within the automated test suite, we + # just assume two instances are equal. No production apps will need multiple, + # differently-configured inflectors running at once ;) + true + end + end +end diff --git a/bridgetown-core/lib/bridgetown-core/layout.rb b/bridgetown-core/lib/bridgetown-core/layout.rb index cbf1a4882..00540c9a6 100644 --- a/bridgetown-core/lib/bridgetown-core/layout.rb +++ b/bridgetown-core/lib/bridgetown-core/layout.rb @@ -2,6 +2,7 @@ module Bridgetown class Layout + using Bridgetown::Refinements include FrontMatter::Importer include LiquidRenderable @@ -67,7 +68,7 @@ def initialize(site, base, name, from_plugin: false) @relative_path = @path.sub(@base_dir, "") @ext = File.extname(name) - @data = read_front_matter(@path)&.with_dot_access + @data = read_front_matter(@path)&.as_dots rescue SyntaxError => e Bridgetown.logger.error "Error:", "Ruby Exception in #{e.message}" diff --git a/bridgetown-core/lib/bridgetown-core/model/base.rb b/bridgetown-core/lib/bridgetown-core/model/base.rb index 909b8c3de..8b56555f8 100644 --- a/bridgetown-core/lib/bridgetown-core/model/base.rb +++ b/bridgetown-core/lib/bridgetown-core/model/base.rb @@ -1,14 +1,9 @@ # frozen_string_literal: true -require "active_model" - module Bridgetown module Model class Base include Bridgetown::RodaCallable - include ActiveModel::Model - extend ActiveModel::Callbacks - define_model_callbacks :load, :save, :destroy class << self def find(id, site: Bridgetown::Current.site) @@ -55,9 +50,7 @@ def build(builder, collection_name, path, data) end def initialize(attributes = {}) - run_callbacks :load do - super - end + self.attributes = attributes end def id @@ -83,9 +76,7 @@ def save raise "`#{origin.class}' doesn't allow writing of model objects" end - run_callbacks :save do - origin.write(self) - end + origin.write(self) end # @return [Bridgetown::Resource::Base] @@ -136,6 +127,10 @@ def attributes @attributes ||= HashWithDotAccess::Hash.new end + def attributes=(new_attributes) + attributes.update new_attributes + end + # Strip out keys like _origin_, _collection_, etc. # @return [HashWithDotAccess::Hash] def data_attributes @@ -152,7 +147,6 @@ def method_missing(method_name, *args) key = method_name.to_s if key.end_with?("=") key.chop! - # attribute_will_change!(key) attributes[key] = args.first return attributes[key] end diff --git a/bridgetown-core/lib/bridgetown-core/model/origin.rb b/bridgetown-core/lib/bridgetown-core/model/origin.rb index 97d72a51c..0b78bc159 100644 --- a/bridgetown-core/lib/bridgetown-core/model/origin.rb +++ b/bridgetown-core/lib/bridgetown-core/model/origin.rb @@ -28,7 +28,7 @@ def verify_model?(klass) return klass.collection_name.to_s == collection_name if klass.respond_to?(:collection_name) - klass.name == ActiveSupport::Inflector.classify(collection_name) + klass.name == site.config.inflector.classify(collection_name) end def read diff --git a/bridgetown-core/lib/bridgetown-core/model/repo_origin.rb b/bridgetown-core/lib/bridgetown-core/model/repo_origin.rb index 83a4f21f4..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] @@ -99,7 +100,7 @@ def exists? private def in_data_collection? - original_path.extname.downcase.in?(self.class.data_file_extensions) && + original_path.extname.downcase.within?(self.class.data_file_extensions) && collection.data? end diff --git a/bridgetown-core/lib/bridgetown-core/rack/boot.rb b/bridgetown-core/lib/bridgetown-core/rack/boot.rb index 9e934c5a8..6c9e7b1a3 100644 --- a/bridgetown-core/lib/bridgetown-core/rack/boot.rb +++ b/bridgetown-core/lib/bridgetown-core/rack/boot.rb @@ -63,7 +63,6 @@ def self.autoload_server_folder( # rubocop:todo Metrics loader.reload loader.eager_load - Bridgetown::Rack::Routes.reload_subclasses rescue SyntaxError => e Bridgetown::Errors.print_build_error(e) end.start @@ -76,7 +75,6 @@ def self.autoload_server_folder( # rubocop:todo Metrics next unless load_path == server_folder loader.eager_load - Bridgetown::Rack::Routes.reload_subclasses end loaders_manager.setup_loaders([server_folder]) diff --git a/bridgetown-core/lib/bridgetown-core/rack/routes.rb b/bridgetown-core/lib/bridgetown-core/rack/routes.rb index 770b603b9..dcc5448fc 100644 --- a/bridgetown-core/lib/bridgetown-core/rack/routes.rb +++ b/bridgetown-core/lib/bridgetown-core/rack/routes.rb @@ -45,9 +45,6 @@ def print_routes end # rubocop:enable Bridgetown/NoPutsAllowed, Metrics/MethodLength - # @return [Hash] - attr_accessor :tracked_subclasses - # @return [Proc] attr_accessor :router_block @@ -59,30 +56,9 @@ def <=>(other) "#{priorities[priority]}#{self}" <=> "#{priorities[other.priority]}#{other}" end - # @param base [Class(Routes)] - def inherited(base) - Bridgetown::Rack::Routes.track_subclass base - super - end - - # @param klass [Class(Routes)] - def track_subclass(klass) - Bridgetown::Rack::Routes.tracked_subclasses ||= {} - Bridgetown::Rack::Routes.tracked_subclasses[klass.name] = klass - end - # @return [Array] def sorted_subclasses - Bridgetown::Rack::Routes.tracked_subclasses&.values&.sort - end - - # @return [void] - def reload_subclasses - Bridgetown::Rack::Routes.tracked_subclasses&.each_key do |klassname| - Kernel.const_get(klassname) - rescue NameError - Bridgetown::Rack::Routes.tracked_subclasses.delete klassname - end + Bridgetown::Rack::Routes.descendants.sort end # Add a router block via the current Routes class diff --git a/bridgetown-core/lib/bridgetown-core/resource/base.rb b/bridgetown-core/lib/bridgetown-core/resource/base.rb index 26e3d75b2..4a9bb1e41 100644 --- a/bridgetown-core/lib/bridgetown-core/resource/base.rb +++ b/bridgetown-core/lib/bridgetown-core/resource/base.rb @@ -3,6 +3,7 @@ module Bridgetown module Resource class Base # rubocop:todo Metrics/ClassLength + using Bridgetown::Refinements include Comparable include Bridgetown::RodaCallable include Bridgetown::Publishable @@ -94,10 +95,7 @@ def relations # # @return [HashWithDotAccess::Hash] def front_matter_defaults - site.frontmatter_defaults.all( - relative_path.to_s, - collection.label.to_sym - ).with_dot_access + site.frontmatter_defaults.all(relative_path.to_s, collection.label.to_sym).as_dots end # @return [HashWithDotAccess::Hash] @@ -349,6 +347,10 @@ def previous_resource alias_method :previous_doc, :previous_resource alias_method :previous, :previous_resource + def deconstruct_keys(...) + @data.value.deconstruct_keys(...) + end + def mark_for_fast_refresh! @fast_refresh_order = site.fast_refresh_ordering site.fast_refresh_ordering += 1 diff --git a/bridgetown-core/lib/bridgetown-core/resource/relations.rb b/bridgetown-core/lib/bridgetown-core/resource/relations.rb index b7d50dc60..5a73389d6 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 @@ -13,6 +15,7 @@ class Relations def initialize(resource) @resource = resource @site = resource.site + @inflector = site.config.inflector end # @return [HashWithDotAccess::Hash] @@ -26,7 +29,7 @@ def relation_types types = [] relation_schema&.each_value do |collections| types << collections - types << Array(collections).map { |item| ActiveSupport::Inflector.pluralize(item) } + types << Array(collections).map { |item| @inflector.pluralize(item) } end types.flatten.uniq end @@ -49,13 +52,13 @@ def resources_for_type(type) end def method_missing(type, *args) - return super unless type.to_s.in?(relation_types) + return super unless type.to_s.within?(relation_types) resources_for_type(type) end def respond_to_missing?(type, *_args) - type.to_s.in?(relation_types) + type.to_s.within?(relation_types) end def to_liquid @@ -70,7 +73,7 @@ def kind_of_relation_for_type(type) relation_schema&.each do |relation_type, collections| collections = Array(collections).then do |collections_arr| collections_arr + - collections_arr.map { |item| ActiveSupport::Inflector.pluralize(item) } + collections_arr.map { |item| @inflector.pluralize(item) } end.flatten.uniq return relation_type if collections.include?(type.to_s) end @@ -79,14 +82,14 @@ def kind_of_relation_for_type(type) # @param type [Symbol] # @return [Bridgetown::Collection] def other_collection_for_type(type) - site.collections[type] || site.collections[ActiveSupport::Inflector.pluralize(type)] + site.collections[type] || site.collections[@inflector.pluralize(type)] end # @return [Array] def collection_labels [ resource.collection.label, - ActiveSupport::Inflector.singularize(resource.collection.label), + @inflector.singularize(resource.collection.label), ] end @@ -109,7 +112,7 @@ def has_many_relation_for_type(type) # rubocop:disable Naming/PredicateName label, singular_label = collection_labels other_collection_for_type(type).resources.select do |other_resource| - resource.data.slug.in?( + resource.data.slug.within?( Array(other_resource.data[label] || other_resource.data[singular_label]) ) end @@ -121,7 +124,7 @@ def has_one_relation_for_type(type) # rubocop:disable Naming/PredicateName label, singular_label = collection_labels other_collection_for_type(type).resources.find do |other_resource| - resource.data.slug.in?( + resource.data.slug.within?( Array(other_resource.data[label] || other_resource.data[singular_label]) ) end diff --git a/bridgetown-core/lib/bridgetown-core/resource/taxonomy_type.rb b/bridgetown-core/lib/bridgetown-core/resource/taxonomy_type.rb index 731da3612..c12bd3662 100644 --- a/bridgetown-core/lib/bridgetown-core/resource/taxonomy_type.rb +++ b/bridgetown-core/lib/bridgetown-core/resource/taxonomy_type.rb @@ -3,6 +3,8 @@ module Bridgetown module Resource class TaxonomyType + using Bridgetown::Refinements + # @return [Bridgetown::Site] attr_reader :site @@ -28,7 +30,7 @@ def initialize(site:, label:, key:, metadata:) def terms site.resources.map do |resource| resource.taxonomies[label].terms - end.flatten.group_by(&:label).with_dot_access + end.flatten.group_by(&:label).as_dots end def inspect diff --git a/bridgetown-core/lib/bridgetown-core/ruby_template_view.rb b/bridgetown-core/lib/bridgetown-core/ruby_template_view.rb index 341a1f93d..747b1a1a5 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 @@ -69,7 +70,7 @@ def liquid_render(component, options = {}, &block) Bridgetown.logger.warn "Liquid Warning:", LiquidRenderer.format_error(e, path || document.relative_path) end - template.render!(options.deep_stringify_keys, _liquid_context).html_safe + template.render!(options.as_dots, _liquid_context).html_safe end def helpers diff --git a/bridgetown-core/lib/bridgetown-core/site.rb b/bridgetown-core/lib/bridgetown-core/site.rb index a196d6c7f..85a91a802 100644 --- a/bridgetown-core/lib/bridgetown-core/site.rb +++ b/bridgetown-core/lib/bridgetown-core/site.rb @@ -61,7 +61,7 @@ def initialize(config, label: :main, loaders_manager: nil) @reader = Reader.new(self) @liquid_renderer = LiquidRenderer.new(self) - Bridgetown::Cache.base_cache["site_tmp"] = {}.with_dot_access + Bridgetown::Cache.base_cache["site_tmp"] = HashWithDotAccess::Hash.new ensure_not_in_dest Bridgetown::Current.sites[@label] = self @@ -73,7 +73,7 @@ def initialize(config, label: :main, loaders_manager: nil) def data=(new_data) @data = new_data - data_hash = @data.to_h.transform_keys(&:to_sym) + data_hash = @data.to_dot_h.transform_keys(&:to_sym) @signals = Bridgetown::Signals.define(*data_hash.keys) do def inspect # rubocop:disable Lint/NestedMethodDefinition var_peeks = instance_variables.filter_map do |var_name| diff --git a/bridgetown-core/lib/bridgetown-core/slot.rb b/bridgetown-core/lib/bridgetown-core/slot.rb index 53895d9dd..87a79cd8f 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] diff --git a/bridgetown-core/lib/bridgetown-core/static_file.rb b/bridgetown-core/lib/bridgetown-core/static_file.rb index b56d4b951..e3b0d4aa9 100644 --- a/bridgetown-core/lib/bridgetown-core/static_file.rb +++ b/bridgetown-core/lib/bridgetown-core/static_file.rb @@ -2,6 +2,7 @@ module Bridgetown class StaticFile + using Bridgetown::Refinements extend Forwardable attr_reader :relative_path, :extname, :name, :data, :site, :collection @@ -34,7 +35,7 @@ def initialize(site, base, dir, name, collection = nil) # rubocop:disable Metric @collection = collection @relative_path = File.join(*[@dir, @name].compact) @extname = File.extname(@name) - @data = @site.frontmatter_defaults.all(relative_path, type).with_dot_access + @data = @site.frontmatter_defaults.all(relative_path, type).as_dots data.permalink ||= if collection && !collection.builtin? "#{collection.default_permalink.chomp("/").chomp(".*")}.*" else diff --git a/bridgetown-core/lib/bridgetown-core/utils.rb b/bridgetown-core/lib/bridgetown-core/utils.rb index 95d6b4f2d..9040e7f39 100644 --- a/bridgetown-core/lib/bridgetown-core/utils.rb +++ b/bridgetown-core/lib/bridgetown-core/utils.rb @@ -2,15 +2,12 @@ module Bridgetown module Utils # rubocop:todo Metrics/ModuleLength - extend Gem::Deprecate extend self - autoload :Ansi, "bridgetown-core/utils/ansi" autoload :Aux, "bridgetown-core/utils/aux" autoload :LoadersManager, "bridgetown-core/utils/loaders_manager" autoload :RequireGems, "bridgetown-core/utils/require_gems" autoload :RubyExec, "bridgetown-core/utils/ruby_exec" autoload :SmartyPantsConverter, "bridgetown-core/utils/smarty_pants_converter" - autoload :PidTracker, "bridgetown-core/utils/pid_tracker" # Constants for use in #slugify SLUGIFY_MODES = %w(raw default pretty simple ascii latin).freeze @@ -72,8 +69,6 @@ def deep_merge_hashes!(target, overwrite) def mergeable?(value) value.is_a?(Hash) || value.is_a?(Drops::Drop) end - alias_method :mergable?, :mergeable? - deprecate :mergable?, :mergeable?, 2023, 7 def duplicable?(obj) case obj diff --git a/bridgetown-core/lib/bridgetown-core/utils/aux.rb b/bridgetown-core/lib/bridgetown-core/utils/aux.rb index d805877ab..1a59b3ad8 100644 --- a/bridgetown-core/lib/bridgetown-core/utils/aux.rb +++ b/bridgetown-core/lib/bridgetown-core/utils/aux.rb @@ -3,48 +3,57 @@ module Bridgetown module Utils module Aux - extend Bridgetown::Utils::PidTracker + class << self + extend Inclusive::Class - def self.with_color(name, message) - return message unless !name.nil? && Bridgetown::Utils::Ansi::COLORS[name.to_sym] + # @return + # [Bridgetown::Foundation::Packages::Ansi, Bridgetown::Foundation::Packages::PidTracker] + packages def utils = [ + Bridgetown::Foundation::Packages::Ansi, + Bridgetown::Foundation::Packages::PidTracker, + ] - Bridgetown::Utils::Ansi.send(name, message) - end + def with_color(name, message) + return message unless !name.nil? && utils.colors[name.to_sym] + + utils.send(name, message) + end - def self.run_process(name, color, cmd, env: {}) - @mutex ||= Thread::Mutex.new - - Thread.new do - rd, wr = IO.pipe("BINARY") - pid = Process.spawn({ "BRIDGETOWN_NO_BUNDLER_REQUIRE" => nil }.merge(env), - cmd, out: wr, err: wr, pgroup: true) - @mutex.synchronize { add_pid(pid, file: :aux) } - - loop do - line = rd.gets - line.to_s.lines.map(&:chomp).each do |message| - next if name == "Frontend" && %r{ELIFECYCLE.*?Command failed}.match?(message) - - output = +"" - output << with_color(color, "[#{name}] ") if color - output << message - @mutex.synchronize do - $stdout.puts output - $stdout.flush + def run_process(name, color, cmd, env: {}) # rubocop:disable Metrics/AbcSize + @mutex ||= Thread::Mutex.new + + Thread.new do + rd, wr = IO.pipe("BINARY") + pid = Process.spawn({ "BRIDGETOWN_NO_BUNDLER_REQUIRE" => nil }.merge(env), + cmd, out: wr, err: wr, pgroup: true) + @mutex.synchronize { utils.add_pid(pid, file: :aux) } + + loop do + line = rd.gets + line.to_s.lines.map(&:chomp).each do |message| + next if name == "Frontend" && %r{ELIFECYCLE.*?Command failed}.match?(message) + + output = +"" + output << with_color(color, "[#{name}] ") if color + output << message + @mutex.synchronize do + $stdout.puts output + $stdout.flush + end end end end end - end - def self.kill_processes - Bridgetown.logger.info "Stopping auxiliary processes..." + def kill_processes + Bridgetown.logger.info "Stopping auxiliary processes..." - read_pidfile(:aux).each do |pid| - Process.kill("SIGTERM", -Process.getpgid(pid.to_i)) - rescue Errno::ESRCH, Errno::EPERM, Errno::ECHILD # rubocop:disable Lint/SuppressedException - ensure - remove_pidfile :aux + utils.read_pidfile(:aux).each do |pid| + Process.kill("SIGTERM", -Process.getpgid(pid.to_i)) + rescue Errno::ESRCH, Errno::EPERM, Errno::ECHILD # rubocop:disable Lint/SuppressedException + ensure + utils.remove_pidfile :aux + end end end end diff --git a/bridgetown-core/lib/roda/plugins/bridgetown_server.rb b/bridgetown-core/lib/roda/plugins/bridgetown_server.rb index 986f0ea54..15e08df4a 100644 --- a/bridgetown-core/lib/roda/plugins/bridgetown_server.rb +++ b/bridgetown-core/lib/roda/plugins/bridgetown_server.rb @@ -142,10 +142,9 @@ def initialize_bridgetown_root # rubocop:todo Metrics/AbcSize module RequestMethods # Monkeypatch Roda/Rack's Request object so it returns a hash which allows for - # indifferent access + # symbol or dot access def cookies - # TODO: maybe replace with a simpler hash that offers an overloaded `[]` method - _previous_roda_cookies.with_indifferent_access + HashWithDotAccess::Hash.new(_previous_roda_cookies) end # Start up the Bridgetown routing system diff --git a/bridgetown-core/lib/site_template/config/initializers.rb b/bridgetown-core/lib/site_template/config/initializers.rb index 7d6f3823f..da947c906 100644 --- a/bridgetown-core/lib/site_template/config/initializers.rb +++ b/bridgetown-core/lib/site_template/config/initializers.rb @@ -15,26 +15,6 @@ # # fast_refresh false - # You can configure the inflector used by Zeitwerk. In v2.0, - # ActiveSupport::Inflector will become the default. - # - # config.inflector = ActiveSupport::Inflector - # - # Add new inflection rules using the following format. Inflections - # are locale specific, and you may define rules for as many different - # locales as you wish. All of these examples are active by default: - # ActiveSupport::Inflector.inflections(:en) do |inflect| - # inflect.plural /^(ox)$/i, "\\1en" - # inflect.singular /^(ox)en/i, "\\1" - # inflect.irregular "person", "people" - # inflect.uncountable %w( fish sheep ) - # end - # - # These inflection rules are supported but not enabled by default: - # ActiveSupport::Inflector.inflections(:en) do |inflect| - # inflect.acronym "RESTful" - # end - # You can use `init` to initialize various Bridgetown features or plugin gems. # For example, you can use the Dotenv gem to load environment variables from # `.env`. Just `bundle add dotenv` and then uncomment this: @@ -63,6 +43,17 @@ # end # + # You can configure the inflector used by Zeitwerk and models. A few acronyms are provided + # by default like HTML, CSS, and JS, so a file like `html_processor.rb` could be defined by + # `HTMLProcessor`. You can add more like so: + # + # config.inflector.configure do |inflections| + # inflections.acronym "W3C" + # end + # + # Bridgetown's inflector is based on Dry::Inflector so you can read up on how to add inflection + # rules here: https://dry-rb.org/gems/dry-inflector/1.0/#custom-inflection-rules + # For more documentation on how to configure your site using this initializers file, # visit: https://edge.bridgetownrb.com/docs/configuration/initializers/ end diff --git a/bridgetown-core/test/features/feature_helper.rb b/bridgetown-core/test/features/feature_helper.rb index 132b1f1a8..168bb906d 100644 --- a/bridgetown-core/test/features/feature_helper.rb +++ b/bridgetown-core/test/features/feature_helper.rb @@ -5,6 +5,8 @@ require "open3" class BridgetownFeatureTest < BridgetownUnitTest + using Bridgetown::Refinements + class Paths SOURCE_DIR = Pathname.new(File.expand_path("../..", __dir__)) @@ -97,7 +99,7 @@ def create_page(file, text, **front_matter) FileUtils.mkdir_p("src") File.write(File.join("src", file), <<~DATA) - #{front_matter.deep_stringify_keys.to_yaml} + #{front_matter.as_dots.to_h.to_yaml} --- #{text} @@ -105,7 +107,7 @@ def create_page(file, text, **front_matter) end def create_configuration(**config) - File.write("bridgetown.config.yml", config.deep_stringify_keys.to_yaml.delete_prefix("---\n")) + File.write("bridgetown.config.yml", config.as_dots.to_h.to_yaml.delete_prefix("---\n")) end def seconds_agnostic_time(time) diff --git a/bridgetown-core/test/helper.rb b/bridgetown-core/test/helper.rb index 68a783ed3..73a239f15 100644 --- a/bridgetown-core/test/helper.rb +++ b/bridgetown-core/test/helper.rb @@ -100,6 +100,9 @@ class BridgetownUnitTest < Minitest::Test include DirectoryHelpers extend DirectoryHelpers + # Uncomment this if you need better printed output when debugging test failures: + # make_my_diffs_pretty! + def mocks_expect(*args) RSpec::Mocks::ExampleMethods::ExpectHost.instance_method(:expect) .bind_call(self, *args) diff --git a/bridgetown-core/test/resources/src/_components/ruby_html_text.rb b/bridgetown-core/test/resources/src/_components/ruby_html_text.rb index eb401955c..be2f79a23 100644 --- a/bridgetown-core/test/resources/src/_components/ruby_html_text.rb +++ b/bridgetown-core/test/resources/src/_components/ruby_html_text.rb @@ -1,4 +1,4 @@ -class RubyHtmlText < Bridgetown::Component +class RubyHTMLText < Bridgetown::Component def template # blub = ->(input, str, replace_str) { input.sub(str, replace_str) } diff --git a/bridgetown-core/test/resources/src/_pages/i-am-ruby.rb b/bridgetown-core/test/resources/src/_pages/i-am-ruby.rb index 9be61938d..5f49eb522 100644 --- a/bridgetown-core/test/resources/src/_pages/i-am-ruby.rb +++ b/bridgetown-core/test/resources/src/_pages/i-am-ruby.rb @@ -14,9 +14,10 @@

Hello #{text "woRld", -> { downcase | strup }}

#{ render "a_partial", abc: 123 } #{ render "an_erb_partial", abc: 456 } + #{ text->{ Bridgetown.refine(self.class).nested_parents }} #{ html-> do if data.title.include?("Ruby") - render RubyHtmlText.new + render RubyHTMLText.new else nil end diff --git a/bridgetown-core/test/source/src/_locales/en.yml b/bridgetown-core/test/source/src/_locales/en.yml index 9b00003ec..2cc483fe1 100644 --- a/bridgetown-core/test/source/src/_locales/en.yml +++ b/bridgetown-core/test/source/src/_locales/en.yml @@ -3,6 +3,7 @@ en: foo: foo bar: bar foo_html: foo + dangerous_html: contacts: bar: foo: foo diff --git a/bridgetown-core/test/ssr/server/routes/cookies.rb b/bridgetown-core/test/ssr/server/routes/cookies.rb index 9da5d964c..fbac73356 100644 --- a/bridgetown-core/test/ssr/server/routes/cookies.rb +++ b/bridgetown-core/test/ssr/server/routes/cookies.rb @@ -4,7 +4,7 @@ class Routes::Cookies < Bridgetown::Rack::Routes route do |r| # route: GET /cookies r.get "cookies" do - { value: r.cookies[:test_key] } + { value: r.cookies.test_key } end # route: POST /cookies diff --git a/bridgetown-core/test/ssr/server/routes/ooh_json.rb b/bridgetown-core/test/ssr/server/routes/ooh_json.rb index 6ba528005..fb4c802a5 100644 --- a/bridgetown-core/test/ssr/server/routes/ooh_json.rb +++ b/bridgetown-core/test/ssr/server/routes/ooh_json.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Routes::OohJson < Bridgetown::Rack::Routes +class Routes::OohJSON < Bridgetown::Rack::Routes route do |r| # route: POST /cookies r.post "ooh_json" do diff --git a/bridgetown-core/test/test_ansi.rb b/bridgetown-core/test/test_ansi.rb deleted file mode 100644 index d33ceec3a..000000000 --- a/bridgetown-core/test/test_ansi.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require "helper" - -class TestAnsi < BridgetownUnitTest - context nil do - setup do - @subject = Bridgetown::Utils::Ansi - end - - Bridgetown::Utils::Ansi::COLORS.each_key do |color| - should "respond_to? #{color}" do - assert @subject.respond_to?(color) - end - end - - should "be able to strip colors" do - assert_equal "hello", @subject.strip(@subject.yellow(@subject.red("hello"))) - end - - should "be able to detect colors" do - assert @subject.has?(@subject.yellow("hello")) - end - end -end diff --git a/bridgetown-core/test/test_configuration.rb b/bridgetown-core/test/test_configuration.rb index e8e28aeea..35fb92807 100644 --- a/bridgetown-core/test/test_configuration.rb +++ b/bridgetown-core/test/test_configuration.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require "helper" -require "colorator" class TestConfiguration < BridgetownUnitTest + using Bridgetown::Refinements + def test_config @test_config ||= { "root_dir" => site_root_dir, @@ -101,57 +102,29 @@ def default_config_fixture(overrides = {}) context "#add_default_collections" do should "no-op if collections is nil" do - result = Configuration[{ "collections" => nil }].add_default_collections + result = Configuration.new(collections: nil).add_default_collections assert_nil result["collections"] end should "turn an array into a hash" do - result = Configuration[{ "collections" => %w(methods) }].add_default_collections - assert_instance_of HashWithDotAccess::Hash, result["collections"] + result = Configuration.new(collections: %w(methods)).add_default_collections + assert_instance_of Configuration, result["collections"] assert_equal({}, result.collections["methods"]) end should "forces posts to output" do - result = Configuration[{ "collections" => { "posts" => { "output" => false } } }] + result = Configuration.new(collections: { "posts" => { "output" => false } }) .add_default_collections assert_equal true, result["collections"]["posts"]["output"] end end - context "#stringify_keys" do - setup do - @mixed_keys = Configuration[{ - "markdown" => "kramdown", - :permalink => "date", - "base_path" => "/", - :include => [".htaccess"], - :source => "./", - }] - - @string_keys = Configuration[{ - "markdown" => "kramdown", - "permalink" => "date", - "base_path" => "/", - "include" => [".htaccess"], - "source" => "./", - }] - end - - should "stringify symbol keys" do - assert_equal @string_keys, @mixed_keys.stringify_keys - end - - should "not mess with keys already strings" do - assert_equal @string_keys, @string_keys.stringify_keys - end - end - context "#config_files" do setup do - @config = Configuration[{ + @config = Configuration.new({ "root_dir" => site_root_dir, "source" => source_dir, - }] + }) @no_override = {} @one_config_file = { "config" => "config.yml" } @multiple_files = { @@ -225,7 +198,7 @@ def default_config_fixture(overrides = {}) context "#read_config_file" do setup do - @config = Configuration[{ "source" => source_dir("empty.yml") }] + @config = Configuration.new({ "source" => source_dir("empty.yml") }) end should "not raise an error on empty files" do @@ -238,7 +211,7 @@ def default_config_fixture(overrides = {}) context "#read_config_files" do setup do - @config = Configuration[{ "source" => source_dir }] + @config = Configuration.new(source: source_dir) end should "continue to read config files if one is empty" do @@ -255,7 +228,7 @@ def default_config_fixture(overrides = {}) context "#check_include_exclude" do setup do - @config = Configuration[{ + @config = Configuration.new({ "auto" => true, "watch" => true, "server" => true, @@ -263,16 +236,16 @@ def default_config_fixture(overrides = {}) "layouts" => true, "data_source" => true, "gems" => [], - }] + }) end should "raise an error if `exclude` key is a string" do - config = Configuration[{ "exclude" => "READ-ME.md, Gemfile,CONTRIBUTING.hello.markdown" }] + config = Configuration.new(exclude: "READ-ME.md, Gemfile,CONTRIBUTING.hello.markdown") assert_raises(Bridgetown::Errors::InvalidConfigurationError) { config.check_include_exclude } end should "raise an error if `include` key is a string" do - config = Configuration[{ "include" => "STOP_THE_PRESSES.txt,.heloses, .git" }] + config = Configuration.new(include: "STOP_THE_PRESSES.txt,.heloses, .git") assert_raises(Bridgetown::Errors::InvalidConfigurationError) { config.check_include_exclude } end end @@ -288,7 +261,7 @@ def default_config_fixture(overrides = {}) raise SystemCallError, "No such file or directory - #{@path}" end allow($stderr).to receive(:puts).with( - Colorator.yellow("Configuration file: none") + "Configuration file: none".yellow ) assert_equal site_configuration, default_config_fixture end @@ -305,11 +278,11 @@ def default_config_fixture(overrides = {}) .to receive(:puts) .and_return( "WARNING: ".rjust(20) + - Colorator.yellow("Error reading configuration. Using defaults (and options).") + "Error reading configuration. Using defaults (and options).".yellow ) allow($stderr) .to receive(:puts) - .and_return(Colorator.yellow("Configuration file: (INVALID) #{@path}")) + .and_return("Configuration file: (INVALID) #{@path}".yellow) assert_equal site_configuration, default_config_fixture end @@ -319,10 +292,10 @@ def default_config_fixture(overrides = {}) end allow($stderr) .to receive(:puts) - .with(Colorator.red( + .with(( "Fatal: ".rjust(20) + \ "The configuration file '#{@user_config}' could not be found." - )) + ).red) assert_raises LoadError do Bridgetown.configuration("config" => [@user_config]) end @@ -421,7 +394,7 @@ def default_config_fixture(overrides = {}) context "#merge_environment_specific_options!" do should "merge options in that are environment-specific" do - conf = Configuration[Bridgetown::Configuration::DEFAULTS.deep_dup] + conf = Configuration.new(Bridgetown::Configuration::DEFAULTS.deep_dup) refute conf["unpublished"] conf["test"] = { "unpublished" => true } conf.merge_environment_specific_options! @@ -432,13 +405,13 @@ def default_config_fixture(overrides = {}) context "#add_default_collections" do should "not do anything if collections is nil" do - conf = Configuration[Bridgetown::Configuration::DEFAULTS.deep_dup].tap { |c| c["collections"] = nil } + conf = Configuration.new(Bridgetown::Configuration::DEFAULTS.deep_dup).tap { |c| c["collections"] = nil } assert_equal conf.add_default_collections, conf assert_nil conf.add_default_collections["collections"] end should "converts collections to a hash if an array" do - conf = Configuration[Bridgetown::Configuration::DEFAULTS.deep_dup].tap do |c| + conf = Configuration.new(Bridgetown::Configuration::DEFAULTS.deep_dup).tap do |c| c["collections"] = ["docs"] end conf.add_default_collections @@ -447,7 +420,7 @@ def default_config_fixture(overrides = {}) end should "force collections.posts.output = true" do - conf = Configuration[Bridgetown::Configuration::DEFAULTS.deep_dup].tap do |c| + conf = Configuration.new(Bridgetown::Configuration::DEFAULTS.deep_dup).tap do |c| c["collections"] = { "posts" => { "output" => false } } end assert conf.add_default_collections.collections.posts.output @@ -455,7 +428,7 @@ def default_config_fixture(overrides = {}) should "leave collections.posts.permalink alone if it is set" do posts_permalink = "/:year/:title/" - conf = Configuration[Bridgetown::Configuration::DEFAULTS.deep_dup].tap do |c| + conf = Configuration.new(Bridgetown::Configuration::DEFAULTS.deep_dup).tap do |c| c["collections"] = { "posts" => { "permalink" => posts_permalink }, } diff --git a/bridgetown-core/test/test_resource.rb b/bridgetown-core/test/test_resource.rb index 82e8fcc9c..dd79b8903 100644 --- a/bridgetown-core/test/test_resource.rb +++ b/bridgetown-core/test/test_resource.rb @@ -329,6 +329,7 @@ class TestResource < BridgetownUnitTest DOES THIS WORK? 123 Does this work? 456 + [Bridgetown]

This is <b>escaped!</b>

piping <i>bad</i> good

yay

diff --git a/bridgetown-core/test/test_ruby_helpers.rb b/bridgetown-core/test/test_ruby_helpers.rb index 395178b12..88505158c 100644 --- a/bridgetown-core/test/test_ruby_helpers.rb +++ b/bridgetown-core/test/test_ruby_helpers.rb @@ -97,6 +97,11 @@ def setup assert @helpers.translate("about.foo_html").html_safe? end + should "return escaped interpolated values within html safe translation" do + assert_equal "", + @helpers.translate("about.dangerous_html", me: "Me") + end + should "not return html safe string when key does not end with _html" do refute @helpers.translate("about.foo").html_safe? end diff --git a/bridgetown-core/test/test_site.rb b/bridgetown-core/test/test_site.rb index b1795d4dc..7c5ca0016 100644 --- a/bridgetown-core/test/test_site.rb +++ b/bridgetown-core/test/test_site.rb @@ -3,6 +3,8 @@ require "helper" class TestSite < BridgetownUnitTest + using Bridgetown::Refinements + context "configuring sites" do should "default base_path to `/`" do site = Site.new(Bridgetown::Configuration::DEFAULTS.deep_dup) diff --git a/bridgetown-foundation/.rubocop.yml b/bridgetown-foundation/.rubocop.yml new file mode 100644 index 000000000..862e28a88 --- /dev/null +++ b/bridgetown-foundation/.rubocop.yml @@ -0,0 +1,7 @@ +--- +inherit_from: ../.rubocop.yml + +AllCops: + Exclude: + - "*.gemspec" + - "script/console" diff --git a/bridgetown-foundation/Rakefile b/bridgetown-foundation/Rakefile new file mode 100644 index 000000000..914cb0d68 --- /dev/null +++ b/bridgetown-foundation/Rakefile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" + +task spec: :test +require "rake/testtask" +Rake::TestTask.new(:test) do |test| + test.libs << "lib" << "test" + test.pattern = "test/**/test_*.rb" + test.verbose = true +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/bridgetown-foundation.gemspec b/bridgetown-foundation/bridgetown-foundation.gemspec new file mode 100644 index 000000000..9145f72a9 --- /dev/null +++ b/bridgetown-foundation/bridgetown-foundation.gemspec @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative "lib/bridgetown/foundation/version" + +Gem::Specification.new do |spec| + spec.name = "bridgetown-foundation" + spec.version = Bridgetown::Foundation::VERSION + spec.author = "Bridgetown Team" + spec.email = "maintainers@bridgetownrb.com" + spec.summary = "Ruby language extensions and other utilities useful for the Bridgetown ecosystem" + spec.homepage = "https://github.com/bridgetownrb/bridgetown/tree/main/bridgetown-foundation" + spec.license = "MIT" + spec.required_ruby_version = ">= 3.1" + + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|script)/!) } + spec.require_paths = ["lib"] + + spec.metadata = { + "source_code_uri" => "https://github.com/bridgetownrb/bridgetown", + "bug_tracker_uri" => "https://github.com/bridgetownrb/bridgetown/issues", + "changelog_uri" => "https://github.com/bridgetownrb/bridgetown/releases", + "homepage_uri" => spec.homepage, + "rubygems_mfa_required" => "true", + } + + spec.add_dependency("hash_with_dot_access", "~> 2.0") + spec.add_dependency("inclusive", "~> 1.0") + spec.add_dependency("zeitwerk", "~> 2.5") +end diff --git a/bridgetown-foundation/lib/bridgetown-foundation.rb b/bridgetown-foundation/lib/bridgetown-foundation.rb new file mode 100644 index 000000000..d4c62c179 --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown-foundation.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "bridgetown/foundation/version" +require "hash_with_dot_access" +require "inclusive" +require "zeitwerk" +require "delegate" + +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 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 + include HashWithDotAccess::Refinements +end + +Zeitwerk.with_loader do |l| + l.push_dir "#{__dir__}/bridgetown/foundation", namespace: Bridgetown::Foundation + l.ignore "#{__dir__}/bridgetown/foundation/version.rb" + l.setup + l.eager_load +end + +module Bridgetown + # Any method call sent will be passed along to the wrapped object with refinements activated + class WrappedObjectWithRefinements < SimpleDelegator + using Bridgetown::Refinements + + # rubocop:disable Style/MissingRespondToMissing + def method_missing(method, ...) = __getobj__.send(method, ...) + # rubocop:enable Style/MissingRespondToMissing + end + + # Call this method to wrap any object(s) in order to use Foundation's refinements + # + # @param *obj [Object] + # @return [WrappedObjectWithRefinements] + def self.refine(*obj) + if obj.length == 1 + WrappedObjectWithRefinements.new(obj[0]) + else + obj.map { WrappedObjectWithRefinements.new _1 } + end + end + + def self.add_refinement(mod, &) + Bridgetown::Refinements.include(mod) + Bridgetown::WrappedObjectWithRefinements.class_eval(&) + end +end diff --git a/bridgetown-core/lib/bridgetown-core/core_ext/class.rb b/bridgetown-foundation/lib/bridgetown/foundation/core_ext/class.rb similarity index 94% rename from bridgetown-core/lib/bridgetown-core/core_ext/class.rb rename to bridgetown-foundation/lib/bridgetown/foundation/core_ext/class.rb index ab918c53c..87d96b63e 100644 --- a/bridgetown-core/lib/bridgetown-core/core_ext/class.rb +++ b/bridgetown-foundation/lib/bridgetown/foundation/core_ext/class.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module Bridgetown +module Bridgetown::Foundation module CoreExt module Class module Descendants diff --git a/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb b/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb new file mode 100644 index 000000000..3cc768c9d --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/core_ext/string.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module CoreExt + module String + module Colorize + class << self + extend Inclusive::Class + + # @return [Bridgetown::Foundation::Packages::Ansi] + public_packages def ansi = [Bridgetown::Foundation::Packages::Ansi] + + def included(klass) + ansi.tap do |a| + a.colors.each_key do |color| + klass.define_method(color) { |*args| a.public_send(color, self, *args) } + end + end + end + end + + # Reset output colors back to a regular string output + def reset_ansi + Colorize.ansi.reset(self) + end + end + + module StartsWithAndEndsWith + def self.included(klass) + klass.alias_method :starts_with?, :start_with? + klass.alias_method :ends_with?, :end_with? + end + end + + ::String.include Colorize, StartsWithAndEndsWith + end + end +end diff --git a/bridgetown-core/lib/bridgetown-core/utils/ansi.rb b/bridgetown-foundation/lib/bridgetown/foundation/packages/ansi.rb similarity index 90% rename from bridgetown-core/lib/bridgetown-core/utils/ansi.rb rename to bridgetown-foundation/lib/bridgetown/foundation/packages/ansi.rb index 121c31289..3770d51cb 100644 --- a/bridgetown-core/lib/bridgetown-core/utils/ansi.rb +++ b/bridgetown-foundation/lib/bridgetown/foundation/packages/ansi.rb @@ -1,33 +1,34 @@ # frozen_string_literal: true -module Bridgetown - module Utils +module Bridgetown::Foundation + module Packages module Ansi extend self ESCAPE = format("%c", 27) MATCH = %r!#{ESCAPE}\[(?:\d+)(?:;\d+)*(j|k|m|s|u|A|B|G)|\e\(B\e\[m!ix COLORS = { + bold: 1, + black: 30, red: 31, green: 32, - black: 30, - magenta: 35, yellow: 33, - white: 37, blue: 34, + magenta: 35, cyan: 36, + white: 37, }.freeze + def colors = COLORS + # Strip ANSI from the current string. It also strips cursor stuff, # well some of it, and it also strips some other stuff that a lot of # the other ANSI strippers don't. - def strip(str) str.gsub MATCH, "" end - # - + # Does the string include ANSI color codes? def has?(str) !!(str =~ MATCH) end @@ -35,7 +36,6 @@ def has?(str) # Reset the color back to the default color so that you do not leak any # colors when you move onto the next line. This is probably normally # used as part of a wrapper so that we don't leak colors. - def reset(str = "") @ansi_reset ||= format("%c[0m", 27) "#{@ansi_reset}#{str}" @@ -44,9 +44,6 @@ def reset(str = "") # SEE: `self::COLORS` for a list of methods. They are mostly # standard base colors supported by pretty much any xterm-color, we do # not need more than the base colors so we do not include them. - # Actually... if I'm honest we don't even need most of the - # base colors. - COLORS.each do |color, num| define_method color do |str| "#{format("%c", 27)}[#{num}m#{str}#{reset}" diff --git a/bridgetown-core/lib/bridgetown-core/utils/pid_tracker.rb b/bridgetown-foundation/lib/bridgetown/foundation/packages/pid_tracker.rb similarity index 92% rename from bridgetown-core/lib/bridgetown-core/utils/pid_tracker.rb rename to bridgetown-foundation/lib/bridgetown/foundation/packages/pid_tracker.rb index 067bab453..39994d090 100644 --- a/bridgetown-core/lib/bridgetown-core/utils/pid_tracker.rb +++ b/bridgetown-foundation/lib/bridgetown/foundation/packages/pid_tracker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -module Bridgetown - module Utils +module Bridgetown::Foundation + module Packages module PidTracker def create_pid_dir FileUtils.mkdir_p pids_dir diff --git a/bridgetown-foundation/lib/bridgetown/foundation/packages/safe_translations.rb b/bridgetown-foundation/lib/bridgetown/foundation/packages/safe_translations.rb new file mode 100644 index 000000000..abdd63508 --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/packages/safe_translations.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module Packages + # NOTE: this is tested by `test/test_ruby_helpers.rb` in bridgetown-core + # + # This is loosely based on the HtmlSafeTranslation module from ActiveSupport, but you can + # actually use it for any kind of safety use case in a translation setting because its + # decoupled from any specific escaping or safety mechanisms. + module SafeTranslations + extend Inclusive::Public + + def translate(key, escaper, safety_method = :html_safe, **options) + safe_options = escape_translation_options(options, escaper) + + i18n_error = false + + exception_handler = ->(*args) do + i18n_error = true + I18n.exception_handler.(*args) + end + + I18n.translate(key, **safe_options, exception_handler:).then do |translation| + i18n_error ? translation : safe_translation(translation, safety_method) + end + end + + public_function :translate + + def escape_translation_options(options, escaper) + @reserved_i18n_keys ||= I18n::RESERVED_KEYS.to_set + + options.to_h do |name, value| + unless @reserved_i18n_keys.include?(name) || (name == :count && value.is_a?(Numeric)) + next [name, escaper.(value)] + end + + [name, value] + end + end + + def safe_translation(translation, safety_method) + @safe_value ||= -> { _1.respond_to?(safety_method) ? _1.send(safety_method) : _1 } + + return translation.map { @safe_value.(_1) } if translation.respond_to?(:map) + + @safe_value.(translation) + end + end + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/questionable_string.rb b/bridgetown-foundation/lib/bridgetown/foundation/questionable_string.rb new file mode 100644 index 000000000..c7d3b1877 --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/questionable_string.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + class QuestionableString < ::String + def method_missing(method_name, *args) + value = method_name.to_s + if value.end_with?("?") + value.chop! + return self == value + end + + super + end + + def respond_to_missing?(method_name, include_private = false) + method_name.end_with?("?") || super + end + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/deep_duplicatable.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/deep_duplicatable.rb new file mode 100644 index 000000000..3258df6e6 --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/deep_duplicatable.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module RefineExt + # This is a very simplistic algorithm…it essentially just works on the Array/Hash values level, + # which for our purposes is fine. + module DeepDuplicatable + refine ::Hash do + def deep_dup + hash = dup + each do |key, value| + hash.delete(key) + if key.is_a?(::String) || key.is_a?(::Symbol) + hash[key] = value.dup + else + hash[key.dup] = if value.is_a?(Array) || value.is_a?(Hash) + value.deep_dup + else + value.dup + end + end + end + hash + end + end + + refine ::Array do + def deep_dep + map do |item| + next item.dup unless item.is_a?(Array) || item.is_a?(Hash) + + item.deep_dup + end + end + end + end + end +end + +module Bridgetown + module Refinements + include Foundation::RefineExt::DeepDuplicatable + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/module.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/module.rb new file mode 100644 index 000000000..d432b2401 --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/module.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module RefineExt + module Module + using RefineExt::Object + + refine ::Module do + def nested_within?(other) + return false if self == other + + other.nested_parents.within?(nested_parents) #[1..]) + end + + def nested_parents + return [] unless name + + nesting_segments = name.split("::")[...-1] + nesting_segments.map.each_with_index do |_nesting_name, index| + Kernel.const_get(nesting_segments[..-(index + 1)].join("::")) + end + end + + def nested_parent + nested_parents.first + end + + def nested_name + name&.split("::")&.last + end + end + end + end +end + +module Bridgetown + module Refinements + include Foundation::RefineExt::Module + end +end diff --git a/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/object.rb b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/object.rb new file mode 100644 index 000000000..ac54c889f --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/refine_ext/object.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Bridgetown::Foundation + module RefineExt + module Object + 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 + # case-insensivitve. + # + # However, for certain comparison types: Module/Class, Hash, and Set, the lesser-than (`<`) + # operator is used instead. This is so you can check `BigDecimal.within? Numeric`, + # `{easy_as: 123}.within?({indeed: "it's true", easy_as: 123})`, and if a Set is a + # `proper_subset?` of another Set. + # + # For Array/Array comparisons, a difference is checked, so `[1,2].within? [3,2,1]` is true, + # but `[1,2].within? [2,3]` is false. + # + # Also for Range, the `cover?` method is used instead of `include?`. + # + # @param other [Object] for determining if receiver lies within this value + # @return [Boolean] + def within?(other) # rubocop:disable Metrics + # rubocop:disable Style/IfUnlessModifier + if is_a?(Module) && other.is_a?(Module) + return self < other + end + + if (is_a?(Hash) && other.is_a?(Hash)) || (is_a?(Set) && other.is_a?(Set)) + return self < other + end + + if is_a?(Array) && other.is_a?(Array) + return false if empty? + + return difference(other).empty? + end + + if other.is_a?(Range) + return other.cover?(self) == true + end + + if is_a?(::String) && other.is_a?(::String) + return other.downcase.include?(downcase) + end + + other&.include?(self) == true + # rubocop:enable Style/IfUnlessModifier + rescue NoMethodError + false + end + + # NOTE: if you _really_ need to preserve Active Support's `in?` functionality, you can just + # require "active_support/core_ext/object/inclusion" + def in?(...) = Bridgetown::Foundation.deprecation_warning( + self, :in?, :within?, 2024, 12 + ).then { within?(...) } + end + 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/version.rb b/bridgetown-foundation/lib/bridgetown/foundation/version.rb new file mode 100644 index 000000000..8f56d11ef --- /dev/null +++ b/bridgetown-foundation/lib/bridgetown/foundation/version.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Bridgetown + module Foundation + # TODO: should we define versions here now and pull it within Core? + VERSION = "1.3.4" + end +end diff --git a/bridgetown-foundation/script/cibuild b/bridgetown-foundation/script/cibuild new file mode 100755 index 000000000..cd643839f --- /dev/null +++ b/bridgetown-foundation/script/cibuild @@ -0,0 +1,6 @@ +#!/bin/sh + +set -ex + +script/fmt +script/test diff --git a/bridgetown-foundation/script/console b/bridgetown-foundation/script/console new file mode 100755 index 000000000..9d1cba79b --- /dev/null +++ b/bridgetown-foundation/script/console @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +require "bundler" +Bundler.setup + +require "bridgetown-foundation" + +module Bridgetown + module Foundation + binding.irb + end +end diff --git a/bridgetown-foundation/script/fmt b/bridgetown-foundation/script/fmt new file mode 100755 index 000000000..427e5332a --- /dev/null +++ b/bridgetown-foundation/script/fmt @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +echo "Rubocop $(bundle exec rubocop --version)" +bundle exec rubocop -D $@ +success=$? +if ((success != 0)); then + echo -e "\nTry running \`script/fmt -a\` to automatically fix errors" +fi +exit $success diff --git a/bridgetown-foundation/script/test b/bridgetown-foundation/script/test new file mode 100755 index 000000000..cfc176981 --- /dev/null +++ b/bridgetown-foundation/script/test @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -e + +# Usage: +# script/test +# script/test + +if [ -d test/dest ] + then rm -r test/dest +fi + +testopts="--profile" + +if [[ $# -lt 1 ]] +then + set -x + time ruby -S bundle exec \ + rake TESTOPTS=$testopts test +else + set -x + time ruby -S bundle exec ruby -I test \ + "$@" $testops +fi diff --git a/bridgetown-foundation/test/test_ansi.rb b/bridgetown-foundation/test/test_ansi.rb new file mode 100644 index 000000000..b0938aadf --- /dev/null +++ b/bridgetown-foundation/test/test_ansi.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "test_helper" + +class TestAnsi < Minitest::Test + include Inclusive + + packages def ansi = [Bridgetown::Foundation::Packages::Ansi] + + Bridgetown::Foundation::Packages::Ansi.colors.each_key do |color| + define_method :"test_respond_to_color_#{color}" do + assert ansi.respond_to?(color) + end + end + + def test_string_color_output + assert_equal "\e[31mred\e[0m", "red".red + end + + def test_able_to_strip_colors + assert_equal "hello", ansi.strip(ansi.yellow(ansi.red("hello"))) + end + + def test_able_to_detect_colors + assert ansi.has?("hello".cyan) + end + + def test_able_to_reset + assert "reset", "reset".reset_ansi + end +end diff --git a/bridgetown-foundation/test/test_helper.rb b/bridgetown-foundation/test/test_helper.rb new file mode 100644 index 000000000..4fa222512 --- /dev/null +++ b/bridgetown-foundation/test/test_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) +require "bridgetown-foundation" + +require "minitest/autorun" + +require "stringio" + +class Minitest::Test + # solution from: https://stackoverflow.com/a/4459463 + def capture_stderr + # The output stream must be an IO-like object. In this case we capture it in + # an in-memory IO object so we can return the string value. You can assign any + # IO object here. + previous_stderr, $stderr = $stderr, StringIO.new + yield + $stderr.string + ensure + # Restore the previous value of stderr (typically equal to STDERR). + $stderr = previous_stderr + end +end diff --git a/bridgetown-foundation/test/test_string.rb b/bridgetown-foundation/test/test_string.rb new file mode 100644 index 000000000..9adf7c88e --- /dev/null +++ b/bridgetown-foundation/test/test_string.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "test_helper" + +class TestString < Minitest::Test + using Bridgetown::Refinements + + def test_that_it_has_a_version_number + refute_nil ::Bridgetown::Foundation::VERSION + end + + def test_string_indentation + assert_equal " it\n is indented\n\n now", "it\n is indented\n\nnow".indent(2) + refute_equal " it\n is indented\n\n now", "it\n is indented\n\nnow".indent(4) + + str_output = +"indent me!" + output = capture_stderr do + str_output.indent!(2, "-") + end + assert_equal " indent me!", str_output + assert_includes output, "multiple arguments aren't supported by `indent!' in Bridgetown" + end + + def test_questionable + assert "test".questionable.test? + refute "test".questionable.nope? + end + + def test_starts_ends_with + assert "this".starts_with?("th") + refute "this".starts_with?("ht") + + assert "this".ends_with?("is") + refute "this".ends_with?("si") + end + + # TODO: more testing of other data types + def test_within + assert "abc".within? %w[def abc] + refute "abc".within? ["def"] + end +end diff --git a/bridgetown-paginate/lib/bridgetown-paginate/hooks.rb b/bridgetown-paginate/lib/bridgetown-paginate/hooks.rb index 20b54095d..04ecc150e 100644 --- a/bridgetown-paginate/lib/bridgetown-paginate/hooks.rb +++ b/bridgetown-paginate/lib/bridgetown-paginate/hooks.rb @@ -3,20 +3,19 @@ # Handles Generated Pages Bridgetown::Hooks.register_one :generated_pages, :post_init, reloadable: false do |page| if page.class != Bridgetown::Paginate::PaginationPage && - page.site.config.dig("pagination", "enabled") - data = page.data.with_dot_access - if (data.pagination.present? && data.pagination.enabled != false) || - (data.paginate.present? && data.paginate.enabled != false) - Bridgetown::Paginate::PaginationGenerator.add_matching_template(page) - end + page.site.config.dig("pagination", "enabled") && ( + page.data.dig(:pagination, :enabled) != false || + page.data.dig(:paginate, :enabled) != false + ) + Bridgetown::Paginate::PaginationGenerator.add_matching_template(page) end end # Handles Resources Bridgetown::Hooks.register_one :resources, :post_read, reloadable: false do |page| if page.site.config.dig("pagination", "enabled") && ( - (page.data.pagination.present? && page.data.pagination.enabled != false) || - (page.data.paginate.present? && page.data.paginate.enabled != false) + page.data.dig(:pagination, :enabled) != false || + page.data.dig(:paginate, :enabled) != false ) Bridgetown::Paginate::PaginationGenerator.add_matching_template(page) end diff --git a/bridgetown-routes/test/ssr/config/adding.rb b/bridgetown-routes/test/ssr/config/adding.rb new file mode 100644 index 000000000..af5bda253 --- /dev/null +++ b/bridgetown-routes/test/ssr/config/adding.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Adding + refine Numeric do + def add(input) + self + input + end + end +end + +module BunchOfRefinements + include Adding +end + +Bridgetown.add_refinement(BunchOfRefinements) do + # boilerplate + using Bridgetown::Refinements + def method_missing(method, ...) = __getobj__.send(method, ...) # rubocop:disable Style +end diff --git a/bridgetown-routes/test/ssr/config/initializers.rb b/bridgetown-routes/test/ssr/config/initializers.rb index d39ec3e12..7a23a26ec 100644 --- a/bridgetown-routes/test/ssr/config/initializers.rb +++ b/bridgetown-routes/test/ssr/config/initializers.rb @@ -8,4 +8,8 @@ config.available_locales = [:en, :it] config.default_locale = :en config.prefix_default_locale = false + + init :adding, require_gem: false + + # puts Bridgetown.refine(Bridgetown.env.to_sym).within?([:test, :production]) # => true end diff --git a/bridgetown-routes/test/ssr/server/routes/initter.rb b/bridgetown-routes/test/ssr/server/routes/initter.rb index 829714c28..6d46c66bc 100644 --- a/bridgetown-routes/test/ssr/server/routes/initter.rb +++ b/bridgetown-routes/test/ssr/server/routes/initter.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true class Routes::Initter < Bridgetown::Rack::Routes + using Bridgetown::Refinements + priority :high route do |r| - r.scope.instance_variable_set(:@answer_to_life, 42) + r.scope.instance_variable_set(:@answer_to_life, 40.add(2)) end end diff --git a/bridgetown-routes/test/ssr/src/_routes/bare_route.rb b/bridgetown-routes/test/ssr/src/_routes/bare_route.rb index ed363b1ca..88764552f 100644 --- a/bridgetown-routes/test/ssr/src/_routes/bare_route.rb +++ b/bridgetown-routes/test/ssr/src/_routes/bare_route.rb @@ -1,6 +1,8 @@ module DoubleNumbers + using Bridgetown::Refinements + def double - self * 2 + self * 1.add(1) end end Numeric.include DoubleNumbers diff --git a/bridgetown-routes/test/ssr/src/_routes/howdy.erb b/bridgetown-routes/test/ssr/src/_routes/howdy.erb index 6926a96bc..6ecee5c2b 100644 --- a/bridgetown-routes/test/ssr/src/_routes/howdy.erb +++ b/bridgetown-routes/test/ssr/src/_routes/howdy.erb @@ -1,6 +1,6 @@ ---<% render_with(data: { - title: "#{params[:yo]} #{@answer_to_life}" + title: "#{params[:yo]} #{@answer_to_life} #{Bridgetown.refine(3).within?([1,2,3])}" }) %>--- diff --git a/bridgetown-routes/test/test_routes.rb b/bridgetown-routes/test/test_routes.rb index cfaee70c3..be00a8ad5 100644 --- a/bridgetown-routes/test/test_routes.rb +++ b/bridgetown-routes/test/test_routes.rb @@ -38,7 +38,7 @@ def site should "return HTML for the howdy route" do get "/howdy?yo=joe&happy=pleased" - assert_equal "

joe 42

\n\n

I am pleasedpleased.

\n", last_response.body + assert_equal "

joe 42 true

\n\n

I am pleasedpleased.

\n", last_response.body end should "return HTML for a route in an arbitrary folder" do diff --git a/bridgetown-website/Gemfile b/bridgetown-website/Gemfile index bf8a0d76e..89b842336 100644 --- a/bridgetown-website/Gemfile +++ b/bridgetown-website/Gemfile @@ -6,6 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "bridgetown", path: "../bridgetown" gem "bridgetown-builder", path: "../bridgetown-builder" gem "bridgetown-core", path: "../bridgetown-core" +gem "bridgetown-foundation", path: "../bridgetown-foundation" gem "bridgetown-paginate", path: "../bridgetown-paginate" gem "puma", "< 7" @@ -19,3 +20,5 @@ gem "gems", "~> 1.2" gem "nokolexbor", "~> 0.4" gem "ruby2js", "~> 5.1" + +gem "inclusive", path: "../../inclusive" diff --git a/bridgetown-website/Gemfile.lock b/bridgetown-website/Gemfile.lock index b67121e45..be3b9852f 100644 --- a/bridgetown-website/Gemfile.lock +++ b/bridgetown-website/Gemfile.lock @@ -1,3 +1,8 @@ +PATH + remote: ../../inclusive + specs: + inclusive (0.1.0) + PATH remote: ../bridgetown-builder specs: @@ -8,16 +13,15 @@ PATH remote: ../bridgetown-core specs: bridgetown-core (1.3.4) - activemodel (>= 6.0, < 8.0) activesupport (>= 6.0, < 8.0) addressable (~> 2.4) amazing_print (~> 1.2) - colorator (~> 1.0) + bridgetown-foundation (= 1.3.4) csv (~> 3.2) + dry-inflector (>= 1.0) erubi (~> 1.9) faraday (~> 2.0) faraday-follow_redirects (~> 0.3) - hash_with_dot_access (~> 1.2) i18n (~> 1.0) kramdown (~> 2.1) kramdown-parser-gfm (~> 1.0) @@ -35,6 +39,13 @@ PATH tilt (~> 2.0) zeitwerk (~> 2.5) +PATH + remote: ../bridgetown-foundation + specs: + bridgetown-foundation (1.3.4) + hash_with_dot_access (~> 2.0) + zeitwerk (~> 2.5) + PATH remote: ../bridgetown-paginate specs: @@ -52,17 +63,22 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (7.0.7.2) - activesupport (= 7.0.7.2) - activesupport (7.0.7.2) + activesupport (7.1.3.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - amazing_print (1.5.0) + amazing_print (1.6.0) ast (2.4.2) + base64 (0.2.0) + bigdecimal (3.1.8) bridgetown-feed (3.1.2) bridgetown (>= 1.2.0.beta2, < 2.0) bridgetown-quick-search (2.0.0) @@ -72,60 +88,63 @@ GEM bridgetown-svg-inliner (2.0.0) bridgetown (>= 1.2.0, < 2.0) nokogiri - colorator (1.1.0) - concurrent-ruby (1.2.2) - csv (3.2.6) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + csv (3.3.0) + drb (2.2.1) + dry-inflector (1.0.0) erubi (1.12.0) - faraday (2.7.10) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) - faraday-net_http (3.0.2) - ffi (1.15.5) + faraday-net_http (3.1.0) + net-http + ffi (1.16.3) gems (1.2.0) - hash_with_dot_access (1.2.0) - activesupport (>= 5.0.0, < 8.0) - i18n (1.14.1) + hash_with_dot_access (2.1.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (5.4.0) - listen (3.8.0) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - minitest (5.19.0) - nio4r (2.7.0) - nokogiri (1.16.3-arm64-darwin) + minitest (5.22.3) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nio4r (2.7.3) + nokogiri (1.16.4-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.3-x86_64-linux) + nokogiri (1.16.4-x86_64-linux) racc (~> 1.4) - nokolexbor (0.5.0-arm64-darwin) - nokolexbor (0.5.0-x86_64-linux) - parser (3.2.2.3) + nokolexbor (0.5.4-arm64-darwin) + nokolexbor (0.5.4-x86_64-linux) + parser (3.3.1.0) ast (~> 2.4.1) racc - public_suffix (5.0.3) + public_suffix (5.0.5) puma (6.4.2) nio4r (~> 2.0) - racc (1.7.1) - rack (3.0.9.1) + racc (1.7.3) + rack (3.0.11) rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rake (13.0.6) + rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) regexp_parser (2.1.1) rexml (3.2.6) - roda (3.71.0) + roda (3.80.0) rack - rouge (4.2.0) - ruby2_keywords (0.0.5) - ruby2js (5.1.0) + rouge (4.2.1) + ruby2js (5.1.2) parser regexp_parser (~> 2.1.1) serbea (2.1.0) @@ -136,12 +155,13 @@ GEM streamlined (0.5.2) serbea (>= 2.1) zeitwerk (~> 2.5) - thor (1.2.2) - tilt (2.2.0) + thor (1.3.1) + tilt (2.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + uri (0.13.0) webrick (1.8.1) - zeitwerk (2.6.11) + zeitwerk (2.6.13) PLATFORMS arm64-darwin-22 @@ -152,11 +172,13 @@ DEPENDENCIES bridgetown-builder! bridgetown-core! bridgetown-feed (~> 3) + bridgetown-foundation! bridgetown-paginate! bridgetown-quick-search (~> 2.0) bridgetown-seo-tag (~> 6.0) bridgetown-svg-inliner (~> 2.0) gems (~> 1.2) + inclusive! nokolexbor (~> 0.4) puma (< 7) ruby2js (~> 5.1) diff --git a/bridgetown-website/config/initializers.rb b/bridgetown-website/config/initializers.rb index 70bb997fe..3913af6cf 100644 --- a/bridgetown-website/config/initializers.rb +++ b/bridgetown-website/config/initializers.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true -Bridgetown.configure do |_config| +Bridgetown.configure do |config| init :"bridgetown-seo-tag" init :"bridgetown-feed" init :"bridgetown-quick-search" init :"bridgetown-svg-inliner" + + config.inflector.configure do |inflections| + inflections.acronym "W3C" + end end diff --git a/bridgetown-website/plugins/builders/inspectors.rb b/bridgetown-website/plugins/builders/html_inspectors.rb similarity index 96% rename from bridgetown-website/plugins/builders/inspectors.rb rename to bridgetown-website/plugins/builders/html_inspectors.rb index f92f19d89..747cb952c 100644 --- a/bridgetown-website/plugins/builders/inspectors.rb +++ b/bridgetown-website/plugins/builders/html_inspectors.rb @@ -1,4 +1,4 @@ -class Builders::Inspectors < SiteBuilder +class Builders::HTMLInspectors < SiteBuilder def build inspect_html do |document| document.query_selector_all("article h2[id], article h3[id]").each do |heading| diff --git a/bridgetown-website/src/_docs/configuration/initializers.md b/bridgetown-website/src/_docs/configuration/initializers.md index 9324abfba..413f5a50c 100644 --- a/bridgetown-website/src/_docs/configuration/initializers.md +++ b/bridgetown-website/src/_docs/configuration/initializers.md @@ -280,21 +280,19 @@ Now anywhere in your Ruby plugins, templates, etc., you can access environment v ### Inflector -Zeitwerk's inflector can be configured to use ActiveSupport::Inflector. This -will become the default in v2.0. +You can configure the inflector used by Zeitwerk and models. A few acronyms are provided by default like HTML, CSS, and JS, so a file like `html_processor.rb` could be defined by `HTMLProcessor`. You can add more inflection rules like so: ```ruby -config.inflector = ActiveSupport::Inflector -``` - -To add new inflection rules, use the following format. - -```ruby -ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.acronym "RESTful" +config.inflector.configure do |inflections| + inflections.acronym "W3C" + inflections.plural "virus", "viruses" # specify a rule for #pluralize + inflections.singular "thieves", "thief" # specify a rule for #singularize end ``` +Bridgetown's inflector is based on `Dry::Inflector`, so you can [read up on how to add inflection +rules here](https://dry-rb.org/gems/dry-inflector/1.0/#custom-inflection-rules). + ### Parse Roda Routes Because of how Roda works via its dynamic routing tree, there's no straightforward way to programmatically list out all the routes in your application. diff --git a/bridgetown-website/src/_docs/resources.md b/bridgetown-website/src/_docs/resources.md index 5fce14797..39b9bfea9 100644 --- a/bridgetown-website/src/_docs/resources.md +++ b/bridgetown-website/src/_docs/resources.md @@ -245,7 +245,7 @@ The three types of relations you can configure are: * **has_one**: a single resource you want to reference will define the slug of the current resource in _its_ front matter * **has_many**: multiple resources you want to reference will define the slug of the current resource in their front matter -The "inflector" is loaded from Rails' ActiveSupport and is used to convert between singular and plural collection names automatically. If you need to customize the inflector with words it doesn't specifically recognize, see configuring ActiveSupport::Inflector in the [`config/initializers.rb`](/docs/configuration/initializers#inflector) file. +The inflections between the various singular and plural relation names are handled by Bridgetown's inflector automatically. If you need to customize the inflector with words it doesn't specifically recognize, you can add additional rules in the [`config/initializers.rb`](/docs/configuration/initializers#inflector) file. ## Configuring Permalinks