Skip to content

Commit

Permalink
Allow route classes to be prioritized to adjust run order (bridgetown…
Browse files Browse the repository at this point in the history
…rb#538)

* Allow route classes to be prioritized to adjust run order

* Add Roda/SSR integration testing — more to come!

* improve route testing

* fix cops

* leave the plugins folder in place

* fix: plugins & components folders can now be absent

* Add site `server_shutdown` hook via Puma

* Move routes prioritization to a concern, better documentation

* Add prioritiziation feature to Builders

* Add documentation for the later value
  • Loading branch information
jaredcwhite authored May 15, 2022
1 parent 42622c7 commit 8b0a13f
Show file tree
Hide file tree
Showing 22 changed files with 302 additions and 56 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ group :test do
gem "minitest-profile"
gem "minitest-reporters"
gem "nokogiri", "~> 1.7"
gem "rack-test"
gem "rspec"
gem "rspec-mocks"
gem "rubocop-bridgetown", "~> 0.3.0", require: false
Expand Down
2 changes: 1 addition & 1 deletion bridgetown-builder/lib/bridgetown-builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module Builders
# SiteBuilder is the superclass sites can subclass to create any number of
# builders, but if the site hasn't defined it explicitly, this is a no-op
if defined?(SiteBuilder)
SiteBuilder.descendants.map do |c|
SiteBuilder.descendants.sort.map do |c|
c.new(c.name, site).build_with_callbacks
end
end
Expand Down
10 changes: 10 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
module Bridgetown
module Builders
class PluginBuilder
include Bridgetown::Prioritizable

self.priorities = {
highest: 100,
high: 10,
normal: 0,
low: -10,
lowest: -100,
}.freeze

include DSL::Generators
include DSL::Helpers
include DSL::Hooks
Expand Down
15 changes: 14 additions & 1 deletion bridgetown-builder/test/test_generators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,32 @@
require "helper"

class GeneratorBuilder < Builder
priority :low

def build
generator do
site.data[:site_metadata][:title] = "Test Title"
end
end
end

class GeneratorBuilder2 < Builder
def build
generator do
site.data[:site_metadata][:title] = "Test Title 2"
end
end
end

class TestGenerators < BridgetownUnitTest
context "creating a generator" do
setup do
Bridgetown.sites.clear
@site = Site.new(site_configuration)
@builder = GeneratorBuilder.new("Generator Test", @site).build_with_callbacks
@builders = [GeneratorBuilder, GeneratorBuilder2].sort
@builders.each_with_index do |builder, index|
builder.new("Generator Test #{index}", @site).build_with_callbacks
end
end

should "be loaded on site setup" do
Expand Down
2 changes: 2 additions & 0 deletions bridgetown-core/lib/bridgetown-core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def require_all(path)

# 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/core_ext/object/blank"
Expand Down Expand Up @@ -90,6 +91,7 @@ module Bridgetown
autoload :LogAdapter, "bridgetown-core/log_adapter"
autoload :PluginContentReader, "bridgetown-core/readers/plugin_content_reader"
autoload :PluginManager, "bridgetown-core/plugin_manager"
autoload :Prioritizable, "bridgetown-core/concerns/prioritizable"
autoload :Publishable, "bridgetown-core/concerns/publishable"
autoload :Publisher, "bridgetown-core/publisher"
autoload :Reader, "bridgetown-core/reader"
Expand Down
3 changes: 3 additions & 0 deletions bridgetown-core/lib/bridgetown-core/commands/start.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ def output_header(mode)
end

cli = Puma::CLI.new puma_args
cli.launcher.events.on_stopped do
Bridgetown::Hooks.trigger :site, :server_shutdown
end
cli.run
end

Expand Down
44 changes: 44 additions & 0 deletions bridgetown-core/lib/bridgetown-core/concerns/prioritizable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Bridgetown
module Prioritizable
module ClassMethods
# @!method priorities
# @return [Hash<Symbol, Object>]

# Get or set the priority of this class. When called without an
# argument it returns the priority. When an argument is given, it will
# set the priority.
#
# @param priority [Symbol] new priority (optional)
# Valid options are: `:lowest`, `:low`, `:normal`, `:high`, `:highest`
# @return [Symbol]
def priority(priority = nil)
@priority ||= nil
@priority = priority if priority && priorities.key?(priority)
@priority || :normal
end

# Spaceship is priority [higher -> lower]
#
# @param other [Class] The class to be compared.
# @return [Integer] -1, 0, 1.
def <=>(other)
priorities[other.priority] <=> priorities[priority]
end
end

def self.included(klass)
klass.extend ClassMethods
klass.class_attribute :priorities, instance_accessor: false
end

# Spaceship is priority [higher -> lower]
#
# @param other [object] The object to be compared.
# @return [Integer] -1, 0, 1.
def <=>(other)
self.class <=> other.class
end
end
end
6 changes: 3 additions & 3 deletions bridgetown-core/lib/bridgetown-core/frontmatter_defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def ensure_time!(set)
# @param path [String] the relative path of the resource
# @param collection [Symbol] :posts, :pages, etc.
#
# @returns [Hash] all default values (an empty hash if there are none)
# @return [Hash] all default values (an empty hash if there are none)
def all(path, collection)
defaults = {}

Expand Down Expand Up @@ -123,7 +123,7 @@ def strip_collections_dir(path)
# @param scope [Hash] the defaults set being asked about
# @param collection [Symbol] the collection of the resource being processed
#
# @returns [Boolean] whether either of the above conditions are satisfied
# @return [Boolean] whether either of the above conditions are satisfied
def applies_collection?(scope, collection)
!scope.key?("collection") || scope["collection"].eql?(collection.to_s)
end
Expand All @@ -132,7 +132,7 @@ def applies_collection?(scope, collection)
#
# @param set [Hash] the default value hash as defined in bridgetown.config.yml
#
# @returns [Boolean] if the set is valid and can be used
# @return [Boolean] if the set is valid and can be used
def valid?(set)
set.is_a?(Hash) && set["values"].is_a?(Hash)
end
Expand Down
41 changes: 5 additions & 36 deletions bridgetown-core/lib/bridgetown-core/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,18 @@
module Bridgetown
class Plugin
extend ActiveSupport::DescendantsTracker
include Bridgetown::Prioritizable

PRIORITIES = {
low: -10,
self.priorities = {
highest: 100,
lowest: -100,
normal: 0,
high: 10,
normal: 0,
low: -10,
lowest: -100,
}.freeze

SourceManifest = Struct.new(:origin, :components, :content, :layouts, keyword_init: true)

# Get or set the priority of this plugin. When called without an
# argument it returns the priority. When an argument is given, it will
# set the priority.
#
# priority - The Symbol priority (default: nil). Valid options are:
# :lowest, :low, :normal, :high, :highest
#
# Returns the Symbol priority.
def self.priority(priority = nil)
@priority ||= nil
@priority = priority if priority && PRIORITIES.key?(priority)
@priority || :normal
end

# Spaceship is priority [higher -> lower]
#
# other - The class to be compared.
#
# Returns -1, 0, 1.
def self.<=>(other)
PRIORITIES[other.priority] <=> PRIORITIES[priority]
end

# Spaceship is priority [higher -> lower]
#
# other - The class to be compared.
#
# Returns -1, 0, 1.
def <=>(other)
self.class <=> other.class
end

# Initialize a new plugin. This should be overridden by the subclass.
#
# config - The Hash of configuration options.
Expand Down
5 changes: 5 additions & 0 deletions bridgetown-core/lib/bridgetown-core/rack/boot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ class << self
attr_accessor :loaders_manager
end

# Start up the Roda Rack application and the Zeitwerk autoloaders. Ensure the
# Roda app is provided the preloaded Bridgetown site configuration. Handle
# any uncaught Roda errors.
#
# @param [Bridgetown::Rack::Roda] optional, defaults to the `RodaApp` constant
def self.boot(roda_app = nil)
self.loaders_manager =
Bridgetown::Utils::LoadersManager.new(Bridgetown::Current.preloaded_configuration)
Expand Down
69 changes: 67 additions & 2 deletions bridgetown-core/lib/bridgetown-core/rack/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,49 @@ class << self
end

class Routes
include Bridgetown::Prioritizable

self.priorities = {
highest: "010",
high: "020",
normal: "030",
low: "040",
lowest: "050",
}.freeze

class << self
attr_accessor :tracked_subclasses, :router_block
# @return [Hash<String, Class(Routes)>]
attr_accessor :tracked_subclasses

# @return [Proc]
attr_accessor :router_block

# Spaceship is priority [higher -> lower]
#
# @param other [Class(Routes)] The class to be compared.
# @return [Integer] -1, 0, 1.
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<Class(Routes)>]
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)
Expand All @@ -30,16 +60,38 @@ def reload_subclasses
end
end

# Add a router block via the current Routes class
#
# Example:
#
# class Routes::Hello < Bridgetown::Rack::Routes
# route do |r|
# r.get "hello", String do |name|
# { hello: "friend #{name}" }
# end
# end
# end
#
# @param block [Proc]
def route(&block)
self.router_block = block
end

# Initialize a new Routes instance and execute the route as part of the
# Roda app request cycle
#
# @param roda_app [Bridgetown::Rack::Roda]
def merge(roda_app)
return unless router_block

new(roda_app).handle_routes
end

# Start the Roda app request cycle. There are two different code paths
# depending on if there's a site `base_path` configured
#
# @param roda_app [Bridgetown::Rack::Roda]
# @return [void]
def start!(roda_app)
if Bridgetown::Current.preloaded_configuration.base_path == "/"
load_all_routes roda_app
Expand All @@ -56,6 +108,12 @@ def start!(roda_app)
nil
end

# Run the Roda public plugin first, set up live reload if allowed, then
# run through all the Routes blocks. If the file-based router plugin
# is available, kick off that request process next.
#
# @param roda_app [Bridgetown::Rack::Roda]
# @return [void]
def load_all_routes(roda_app)
roda_app.request.public

Expand All @@ -64,7 +122,7 @@ def load_all_routes(roda_app)
setup_live_reload roda_app
end

Bridgetown::Rack::Routes.tracked_subclasses&.each_value do |klass|
Bridgetown::Rack::Routes.sorted_subclasses&.each do |klass|
klass.merge roda_app
end

Expand All @@ -73,6 +131,7 @@ def load_all_routes(roda_app)
Bridgetown::Routes::RodaRouter.start!(roda_app)
end

# @param app [Bridgetown::Rack::Roda]
def setup_live_reload(app) # rubocop:disable Metrics/AbcSize
sleep_interval = 0.2
file_to_check = File.join(app.class.opts[:bridgetown_preloaded_config].destination,
Expand Down Expand Up @@ -102,14 +161,20 @@ def setup_live_reload(app) # rubocop:disable Metrics/AbcSize
end
end

# @param roda_app [Bridgetown::Rack::Roda]
def initialize(roda_app)
@_roda_app = roda_app
end

# Execute the router block via the instance, passing it the Roda request
#
# @return [Object] whatever is returned by the router block as expected
# by the Roda API
def handle_routes
instance_exec(@_roda_app.request, &self.class.router_block)
end

# Any missing methods are passed along to the underlying Roda app if possible
def method_missing(method_name, *args, **kwargs, &block)
if @_roda_app.respond_to?(method_name.to_sym)
@_roda_app.send method_name.to_sym, *args, **kwargs, &block
Expand Down
7 changes: 3 additions & 4 deletions bridgetown-core/lib/bridgetown-core/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ def watch(site, options, &block)
#
# @param (see #watch)
def load_paths_to_watch(site, options)
site.plugin_manager.plugins_path.select { |path| Dir.exist?(path) }
.then do |paths|
(paths + options.autoload_paths).uniq
end
(site.plugin_manager.plugins_path + options.autoload_paths).uniq.select do |path|
Dir.exist?(path)
end
end

# Start a listener to watch for changes and call {#reload_site}
Expand Down
Loading

0 comments on commit 8b0a13f

Please sign in to comment.