Skip to content

Commit

Permalink
Plugin content and autoreload (#41)
Browse files Browse the repository at this point in the history
* Add Concurrently and BrowserSync for live reload

* Plugin manifest for Gem-based content (thems, etc.)

* Add auto-reload functionality for local plugins

* Additional improvements to support plugin reloading

* Refactor hooks and support reloading via the watcher

* Add unit tests for PluginSourceManifest etc.

* New plugins CLI

* Gotta stick with yield_self for Ruby 2.5

* Add support for the upcoming SiteBuilder abstract class

* First pass at integrating Builder DSL

* Fix SiteBuilder instantiation logic

* Add scripts and Rubocop config to Builder gem

* Set up classes with autoload for performance

* Reformat builder code using Rubocop guidelines

* Rename classes and hooks for best usage

* Data hashes are now HashWithIndifferentAccess

* Remove unnecessary hash utils

* remove test file

* Move DSL into mixins, add Builder test suite

* Add Builder to local Gemfile

* Add additional Builder tests

* Test the automatic instantiation of SiteBuilder

* Add convenience method to PluginManager

* Allow method symbols as alternative to blocks

* Simplify and don't use instance_exec for generators/tags

* define_singleton_method to the rescue!

* Move HTTP DSL to mixin and add tests

* Update begin/rescue blocks to Ruby 2.5
  • Loading branch information
jaredcwhite authored May 13, 2020
1 parent 56871ff commit 5aca30f
Show file tree
Hide file tree
Showing 84 changed files with 1,596 additions and 274 deletions.
18 changes: 12 additions & 6 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ require:
Bridgetown/NoPutsAllowed:
Exclude:
- rake/*.rake
- bridgetown-core/lib/bridgetown-core/commands/plugins.rb

AllCops:
TargetRubyVersion: 2.4
TargetRubyVersion: 2.5
Include:
- bridgetown-core/lib/**/*.rb
- bridgetown-core/test/**/*.rb
Expand Down Expand Up @@ -54,6 +55,8 @@ Lint/Void:
- bridgetown-core/lib/bridgetown-core/site.rb
Metrics/AbcSize:
Max: 21
Exclude:
- bridgetown-core/lib/bridgetown-core/commands/plugins.rb
Metrics/BlockLength:
Exclude:
- bridgetown-core/test/**/*.rb
Expand All @@ -72,6 +75,7 @@ Metrics/CyclomaticComplexity:
Exclude:
- bridgetown-core/lib/bridgetown-core/utils.rb
- bridgetown-core/lib/bridgetown-core/commands/serve.rb
- bridgetown-core/lib/bridgetown-core/commands/plugins.rb
Layout/LineLength:
Exclude:
- !ruby/regexp /bridgetown-core/features\/.*.rb/
Expand All @@ -84,25 +88,27 @@ Metrics/MethodLength:
CountComments: false
Max: 20
Severity: error
Exclude:
- bridgetown-core/lib/bridgetown-core/commands/plugins.rb
Metrics/ModuleLength:
Max: 240
Exclude:
- bridgetown-core/lib/bridgetown-core/filters.rb
Metrics/ParameterLists:
Max: 4
Exclude:
- bridgetown-core/lib/bridgetown-core/hooks.rb
Metrics/PerceivedComplexity:
Max: 8
Exclude:
- bridgetown-core/lib/bridgetown-core/commands/plugins.rb
Naming/FileName:
Enabled: false
Naming/HeredocDelimiterNaming:
Exclude:
- bridgetown-core/test/**/*.rb
Naming/MemoizedInstanceVariableName:
Exclude:
- bridgetown-core/lib/bridgetown-core/convertible.rb
- bridgetown-core/lib/bridgetown-core/drops/site_drop.rb
- bridgetown-core/lib/bridgetown-core/drops/unified_payload_drop.rb
- bridgetown-core/lib/bridgetown-core/page_without_a_file.rb
Enabled: false
Security/MarshalLoad:
Exclude:
- !ruby/regexp /bridgetown-core/test\/.*.rb$/
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ end

# Bridgetown
gem 'bridgetown-core', path: 'bridgetown-core'
gem 'bridgetown-builder', path: 'bridgetown-builder'
gem 'bridgetown-paginate', path: 'bridgetown-paginate'
6 changes: 4 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# frozen_string_literal: true

task default: %w[test_all]
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-builder && script/cibuild"
sh "cd bridgetown-paginate && script/cibuild"
end

task :release_all_unsafe do
sh "cd bridgetown && bundle exec rake release"
sh "cd bridgetown-core && bundle exec rake release"
sh "cd bridgetown-builder && bundle exec rake release"
sh "cd bridgetown-paginate && bundle exec rake release"
end

desc "Build and release all Bridgetown gems in monorepo"
task release_all: %w[test_all release_all_unsafe]
task release_all: %w(test_all release_all_unsafe)
17 changes: 17 additions & 0 deletions bridgetown-builder/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
inherit_from: ../.rubocop.yml

AllCops:
Include:
- lib/**/*.rb
- spec/**/*.rb

Metrics/AbcSize:
Exclude:
- lib/bridgetown-builder/dsl/*
Metrics/MethodLength:
Exclude:
- lib/bridgetown-builder/dsl/*
Metrics/ParameterLists:
Exclude:
- lib/bridgetown-builder/dsl/hooks.rb
11 changes: 11 additions & 0 deletions bridgetown-builder/Rakefile
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions bridgetown-builder/bridgetown-builder.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require_relative "../bridgetown-core/lib/bridgetown-core/version"

Gem::Specification.new do |spec|
spec.name = "bridgetown-builder"
spec.version = Bridgetown::VERSION
spec.author = "Bridgetown Team"
spec.email = "[email protected]"
spec.summary = "A Bridgetown plugin to provide a sophisticated DSL for writing plugins at a higher level of abstraction."
spec.homepage = "https://github.com/bridgetownrb/bridgetown/tree/master/bridgetown-builder"
spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r!^(test|script|spec|features)/!) }
spec.require_paths = ["lib"]

spec.add_dependency("bridgetown-core", Bridgetown::VERSION)
end
41 changes: 41 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require "bridgetown-core"
require "bridgetown-core/version"

module Bridgetown
module Builders
autoload :PluginBuilder, "bridgetown-builder/plugin"
autoload :DocumentBuilder, "bridgetown-builder/document"
autoload :DocumentsGenerator, "bridgetown-builder/documents_generator"
end

autoload :Builder, "bridgetown-builder/builder"
end

Bridgetown::Hooks.register_one :site, :pre_read, priority: :low, reloadable: false do |site|
# 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|
c.new(c.name, site)
end
end

# If the documents generator is in use, we need to add it at the top of the
# list so the site runs the generator before any others
if Bridgetown::Builders.autoload?(:DocumentsGenerator).nil? &&
!site.generators.first.is_a?(Bridgetown::Builders::DocumentsGenerator)
site.generators.unshift Bridgetown::Builders::DocumentsGenerator.new(site.config)
end
end

Bridgetown::Hooks.register_one :site, :pre_reload, reloadable: false do |site|
# Remove all anonymous generator classes so they can later get reloaded
site.converters.delete_if { |generator| generator.class.name.nil? }
site.generators.delete_if { |generator| generator.class.name.nil? }

unless Bridgetown::Builders.autoload? :DocumentsGenerator
Bridgetown::Builders::DocumentsGenerator.clear_documents_to_generate
end
end
45 changes: 45 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder/builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module Bridgetown
# Superclass for a website's SiteBuilder abstract class
class Builder < Bridgetown::Builders::PluginBuilder
class << self
def register
Bridgetown::Hooks.register_one :site, :pre_read, reloadable: false do |site|
new(name, site)
end
end
end

# Subclass is expected to implement #build
def initialize(name, current_site = nil)
super(name, current_site)
build
end

def inspect
name
end

def self.inherited(const)
(@children ||= Set.new).add const
catch_inheritance(const) do |const_|
catch_inheritance(const_)
end
end

def self.catch_inheritance(const)
const.define_singleton_method :inherited do |const_|
(@children ||= Set.new).add const_
yield const_ if block_given?
end
end

def self.descendants
@children ||= Set.new
out = @children.map(&:descendants)
out << self unless ["SiteBuilder", "Bridgetown::Builder"].include?(name)
Set.new(out).flatten
end
end
end
63 changes: 63 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder/document.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

module Bridgetown
module Builders
class DocumentBuilder
attr_reader :site

def initialize(site, path)
@site = site
@path = path
@data = ActiveSupport::HashWithIndifferentAccess.new
end

def front_matter(data)
@data.merge!(data)
end

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def _add_document_to_site
@collection = (@data[:collection] || :posts).to_s
collection = @site.collections[@collection]
unless collection
collection = Collection.new(@site, @collection)
collection.metadata["output"] = true
@site.collections[@collection] = collection
end

doc = Document.new(
File.join(collection.directory, @path),
site: @site,
collection: collection
)
doc.send(:merge_defaults)
doc.content = @data[:content]
@data.delete(:content)

if @path.start_with?("/")
pathname = Pathname.new(@path)
@data[:permalink] = File.join(
pathname.dirname,
pathname.basename.sub(pathname.extname, "")
) + "/"
end

doc.merge_data!(@data)
doc.send(:read_post_data)

collection.docs << doc
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength

# rubocop:disable Style/MissingRespondToMissing
def method_missing(key, value = nil)
if respond_to?(key)
super
else
@data[key] = value
end
end
# rubocop:enable Style/MissingRespondToMissing
end
end
end
31 changes: 31 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder/documents_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Bridgetown
module Builders
class DocumentsGenerator < Bridgetown::Generator
priority :high

def self.add(path, block)
@documents_to_generate ||= []
@documents_to_generate << [path, block]
end

class << self
attr_reader :documents_to_generate
end

def self.clear_documents_to_generate
@documents_to_generate = []
end

def generate(site)
self.class.documents_to_generate&.each do |doc_block|
path, block = doc_block
doc_builder = DocumentBuilder.new(site, path)
doc_builder.instance_exec(&block)
doc_builder._add_document_to_site
end
end
end
end
end
34 changes: 34 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder/dsl/generators.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module Bridgetown
module Builders
module DSL
module Generators
def generator(method_name = nil, &block)
block = method(method_name) if method_name.is_a?(Symbol)
local_name = name # pull the name method into a local variable

new_gen = Class.new(Bridgetown::Generator) do
define_method(:_builder_block) { block }
define_singleton_method(:custom_name) { local_name }

attr_reader :site

def inspect
"#{self.class.custom_name} (Generator)"
end

def generate(_site)
_builder_block.call
end
end

first_low_priority_index = site.generators.find_index { |gen| gen.class.priority == :low }
site.generators.insert(first_low_priority_index || 0, new_gen.new(site.config))

functions << { name: name, generator: new_gen }
end
end
end
end
end
28 changes: 28 additions & 0 deletions bridgetown-builder/lib/bridgetown-builder/dsl/hooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Bridgetown
module Builders
module DSL
module Hooks
def hook(
owner,
event,
method_name = nil,
priority: Bridgetown::Hooks::DEFAULT_PRIORITY,
&block
)
block = method(method_name) if method_name.is_a?(Symbol)

hook_block = Bridgetown::Hooks.register_one(owner, event, priority: priority, &block)
functions << { name: name, hook: [owner, event, priority, hook_block] }
end

def add_data(data_key)
hook(:site, :post_read) do
site.data[data_key] = yield
end
end
end
end
end
end
Loading

0 comments on commit 5aca30f

Please sign in to comment.