bridgetown-core 0.13.0 → 0.15.0.beta3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +3 -1
- data/bin/bridgetown +9 -48
- data/bridgetown-core.gemspec +6 -2
- data/lib/bridgetown-core.rb +13 -3
- data/lib/bridgetown-core/cleaner.rb +1 -0
- data/lib/bridgetown-core/commands/apply.rb +73 -0
- data/lib/bridgetown-core/commands/base.rb +45 -0
- data/lib/bridgetown-core/commands/build.rb +91 -86
- data/lib/bridgetown-core/commands/clean.rb +30 -29
- data/lib/bridgetown-core/commands/concerns/actions.rb +123 -0
- data/lib/bridgetown-core/commands/concerns/build_options.rb +76 -0
- data/lib/bridgetown-core/commands/concerns/configuration_overridable.rb +18 -0
- data/lib/bridgetown-core/commands/concerns/summarizable.rb +13 -0
- data/lib/bridgetown-core/commands/console.rb +46 -39
- data/lib/bridgetown-core/commands/doctor.rb +126 -127
- data/lib/bridgetown-core/commands/new.rb +120 -158
- data/lib/bridgetown-core/commands/plugins.rb +206 -0
- data/lib/bridgetown-core/commands/registrations.rb +16 -0
- data/lib/bridgetown-core/commands/serve.rb +214 -215
- data/lib/bridgetown-core/{convertible.rb → concerns/convertible.rb} +3 -6
- data/lib/bridgetown-core/concerns/site/configurable.rb +153 -0
- data/lib/bridgetown-core/concerns/site/content.rb +111 -0
- data/lib/bridgetown-core/concerns/site/extensible.rb +56 -0
- data/lib/bridgetown-core/concerns/site/processable.rb +74 -0
- data/lib/bridgetown-core/concerns/site/renderable.rb +49 -0
- data/lib/bridgetown-core/concerns/site/writable.rb +31 -0
- data/lib/bridgetown-core/configuration.rb +2 -9
- data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +0 -3
- data/lib/bridgetown-core/document.rb +1 -1
- data/lib/bridgetown-core/drops/page_drop.rb +1 -1
- data/lib/bridgetown-core/drops/site_drop.rb +1 -1
- data/lib/bridgetown-core/excerpt.rb +4 -1
- data/lib/bridgetown-core/external.rb +17 -21
- data/lib/bridgetown-core/filters.rb +10 -0
- data/lib/bridgetown-core/generators/prototype_generator.rb +3 -1
- data/lib/bridgetown-core/hooks.rb +62 -62
- data/lib/bridgetown-core/layout.rb +10 -4
- data/lib/bridgetown-core/liquid_renderer.rb +1 -0
- data/lib/bridgetown-core/liquid_renderer/file.rb +1 -4
- data/lib/bridgetown-core/liquid_renderer/file_system.rb +3 -1
- data/lib/bridgetown-core/page.rb +11 -19
- data/lib/bridgetown-core/plugin.rb +2 -0
- data/lib/bridgetown-core/plugin_manager.rb +88 -21
- data/lib/bridgetown-core/reader.rb +5 -0
- data/lib/bridgetown-core/readers/data_reader.rb +5 -2
- data/lib/bridgetown-core/readers/layout_reader.rb +9 -2
- data/lib/bridgetown-core/readers/plugin_content_reader.rb +48 -0
- data/lib/bridgetown-core/renderer.rb +38 -28
- data/lib/bridgetown-core/site.rb +20 -463
- data/lib/bridgetown-core/tags/include.rb +12 -0
- data/lib/bridgetown-core/tags/render_content.rb +29 -16
- data/lib/bridgetown-core/tags/with.rb +15 -0
- data/lib/bridgetown-core/utils.rb +45 -27
- data/lib/bridgetown-core/utils/ruby_exec.rb +1 -4
- data/lib/bridgetown-core/version.rb +2 -2
- data/lib/bridgetown-core/watcher.rb +21 -10
- data/lib/site_template/Gemfile.erb +19 -0
- data/lib/site_template/package.json +1 -0
- data/lib/site_template/plugins/{.keep → builders/.keep} +0 -0
- data/lib/site_template/plugins/site_builder.rb +4 -0
- data/lib/site_template/src/_components/footer.html +3 -0
- data/lib/site_template/src/_components/head.html +9 -0
- data/lib/site_template/src/{_includes → _components}/navbar.html +1 -0
- data/lib/site_template/src/_layouts/default.html +3 -3
- data/lib/site_template/src/posts.md +15 -0
- data/lib/site_template/start.js +1 -1
- data/lib/site_template/webpack.config.js +3 -3
- metadata +90 -18
- data/lib/bridgetown-core/command.rb +0 -106
- data/lib/bridgetown-core/commands/help.rb +0 -34
- data/lib/site_template/src/_components/.keep +0 -0
- data/lib/site_template/src/_includes/footer.html +0 -3
- data/lib/site_template/src/_includes/head.html +0 -9
@@ -2,8 +2,33 @@
|
|
2
2
|
|
3
3
|
module Bridgetown
|
4
4
|
class PluginManager
|
5
|
+
PLUGINS_GROUP = :bridgetown_plugins
|
6
|
+
|
5
7
|
attr_reader :site
|
6
8
|
|
9
|
+
@source_manifests = Set.new
|
10
|
+
@registered_plugins = Set.new
|
11
|
+
|
12
|
+
def self.add_source_manifest(source_manifest)
|
13
|
+
unless source_manifest.is_a?(Bridgetown::Plugin::SourceManifest)
|
14
|
+
raise "You must add a SourceManifest instance"
|
15
|
+
end
|
16
|
+
|
17
|
+
@source_manifests << source_manifest
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new_source_manifest(*args)
|
21
|
+
add_source_manifest(Bridgetown::Plugin::SourceManifest.new(*args))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_registered_plugin(gem_or_plugin_file)
|
25
|
+
@registered_plugins << gem_or_plugin_file
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
attr_reader :source_manifests, :registered_plugins
|
30
|
+
end
|
31
|
+
|
7
32
|
# Create an instance of this class.
|
8
33
|
#
|
9
34
|
# site - the instance of Bridgetown::Site we're concerned with
|
@@ -13,22 +38,23 @@ module Bridgetown
|
|
13
38
|
@site = site
|
14
39
|
end
|
15
40
|
|
16
|
-
# Require all the plugins which are allowed.
|
17
|
-
#
|
18
|
-
# Returns nothing
|
19
|
-
def conscientious_require
|
20
|
-
require_plugin_files
|
21
|
-
end
|
22
|
-
|
23
41
|
def self.require_from_bundler
|
24
42
|
if !ENV["BRIDGETOWN_NO_BUNDLER_REQUIRE"] && File.file?("Gemfile")
|
25
43
|
require "bundler"
|
26
44
|
|
27
|
-
Bundler.
|
28
|
-
required_gems
|
45
|
+
required_gems = Bundler.require PLUGINS_GROUP
|
46
|
+
required_gems.select! do |dep|
|
47
|
+
(dep.groups & [PLUGINS_GROUP]).any? && dep.should_include?
|
48
|
+
end
|
49
|
+
|
29
50
|
install_yarn_dependencies(required_gems)
|
30
|
-
|
31
|
-
|
51
|
+
|
52
|
+
required_gems.each do |installed_gem|
|
53
|
+
add_registered_plugin installed_gem
|
54
|
+
end
|
55
|
+
|
56
|
+
Bridgetown.logger.debug("PluginManager:",
|
57
|
+
"Required #{required_gems.map(&:name).join(", ")}")
|
32
58
|
ENV["BRIDGETOWN_NO_BUNDLER_REQUIRE"] = "true"
|
33
59
|
|
34
60
|
true
|
@@ -47,28 +73,69 @@ module Bridgetown
|
|
47
73
|
package_json = JSON.parse(File.read("package.json"))
|
48
74
|
|
49
75
|
required_gems.each do |loaded_gem|
|
50
|
-
|
51
|
-
|
52
|
-
yarn_add_dependency = loaded_gem.to_spec.metadata["yarn-add"].split("@")
|
53
|
-
next unless yarn_add_dependency.length == 2
|
54
|
-
|
55
|
-
# check matching version number is see if it's already installed
|
56
|
-
current_package = package_json["dependencies"].dig(yarn_add_dependency.first)
|
57
|
-
next unless current_package.nil? || current_package != yarn_add_dependency.last
|
76
|
+
yarn_dependency = find_yarn_dependency(loaded_gem)
|
77
|
+
next unless add_yarn_dependency?(yarn_dependency, package_json)
|
58
78
|
|
59
79
|
# all right, time to install the package
|
60
|
-
cmd = "yarn add #{
|
80
|
+
cmd = "yarn add #{yarn_dependency.join("@")}"
|
61
81
|
system cmd
|
62
82
|
end
|
63
83
|
end
|
64
84
|
|
85
|
+
def self.find_yarn_dependency(loaded_gem)
|
86
|
+
yarn_dependency = loaded_gem.to_spec&.metadata&.dig("yarn-add")&.split("@")
|
87
|
+
return nil if yarn_dependency&.length != 2
|
88
|
+
|
89
|
+
yarn_dependency
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.add_yarn_dependency?(yarn_dependency, package_json)
|
93
|
+
return false if yarn_dependency.nil?
|
94
|
+
|
95
|
+
# check matching version number is see if it's already installed
|
96
|
+
if package_json["dependencies"]
|
97
|
+
current_version = package_json["dependencies"].dig(yarn_dependency.first)
|
98
|
+
package_requires_updating?(current_version, yarn_dependency.last)
|
99
|
+
else
|
100
|
+
true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.package_requires_updating?(current_version, dep_version)
|
105
|
+
current_version.nil? || current_version != dep_version && !current_version.include?("/")
|
106
|
+
end
|
107
|
+
|
65
108
|
# Require all .rb files
|
66
109
|
#
|
67
110
|
# Returns nothing.
|
68
111
|
def require_plugin_files
|
69
112
|
plugins_path.each do |plugin_search_path|
|
70
113
|
plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
|
71
|
-
|
114
|
+
|
115
|
+
# Require "site_builder.rb" first if present so subclasses can all
|
116
|
+
# inherit from SiteBuilder without needing explicit require statements
|
117
|
+
sorted_plugin_files = plugin_files.select do |path|
|
118
|
+
path.include?("site_builder.rb")
|
119
|
+
end + plugin_files.reject do |path|
|
120
|
+
path.include?("site_builder.rb")
|
121
|
+
end
|
122
|
+
|
123
|
+
sorted_plugin_files.each do |plugin_file|
|
124
|
+
self.class.add_registered_plugin plugin_file
|
125
|
+
end
|
126
|
+
Bridgetown::External.require_with_graceful_fail(sorted_plugin_files)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Reload .rb plugin files via the watcher
|
131
|
+
def reload_plugin_files
|
132
|
+
plugins_path.each do |plugin_search_path|
|
133
|
+
plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
|
134
|
+
Array(plugin_files).each do |name|
|
135
|
+
Bridgetown.logger.debug "Reloading:", name.to_s
|
136
|
+
self.class.add_registered_plugin name
|
137
|
+
load name
|
138
|
+
end
|
72
139
|
end
|
73
140
|
end
|
74
141
|
|
@@ -11,6 +11,7 @@ module Bridgetown
|
|
11
11
|
# Read Site data from disk and load it into internal data structures.
|
12
12
|
#
|
13
13
|
# Returns nothing.
|
14
|
+
# rubocop:disable Metrics/AbcSize
|
14
15
|
def read
|
15
16
|
@site.layouts = LayoutReader.new(site).read
|
16
17
|
read_directories
|
@@ -18,7 +19,11 @@ module Bridgetown
|
|
18
19
|
sort_files!
|
19
20
|
@site.data = DataReader.new(site).read(site.config["data_dir"])
|
20
21
|
CollectionReader.new(site).read
|
22
|
+
Bridgetown::PluginManager.source_manifests.map(&:content).compact.each do |plugin_content_dir|
|
23
|
+
PluginContentReader.new(site, plugin_content_dir).read
|
24
|
+
end
|
21
25
|
end
|
26
|
+
# rubocop:enable Metrics/AbcSize
|
22
27
|
|
23
28
|
# Sorts posts, pages, and static files.
|
24
29
|
def sort_files!
|
@@ -5,7 +5,7 @@ module Bridgetown
|
|
5
5
|
attr_reader :site, :content
|
6
6
|
def initialize(site)
|
7
7
|
@site = site
|
8
|
-
@content =
|
8
|
+
@content = ActiveSupport::HashWithIndifferentAccess.new
|
9
9
|
@entry_filter = EntryFilter.new(site)
|
10
10
|
end
|
11
11
|
|
@@ -41,7 +41,10 @@ module Bridgetown
|
|
41
41
|
next if @entry_filter.symlink?(path)
|
42
42
|
|
43
43
|
if File.directory?(path)
|
44
|
-
read_data_to(
|
44
|
+
read_data_to(
|
45
|
+
path,
|
46
|
+
data[sanitize_filename(entry)] = ActiveSupport::HashWithIndifferentAccess.new
|
47
|
+
)
|
45
48
|
else
|
46
49
|
key = sanitize_filename(File.basename(entry, ".*"))
|
47
50
|
data[key] = read_data_file(path)
|
@@ -14,6 +14,13 @@ module Bridgetown
|
|
14
14
|
Layout.new(site, layout_directory, layout_file)
|
15
15
|
end
|
16
16
|
|
17
|
+
Bridgetown::PluginManager.source_manifests.map(&:layouts).compact.each do |plugin_layouts|
|
18
|
+
layout_entries(plugin_layouts).each do |layout_file|
|
19
|
+
@layouts[layout_name(layout_file)] ||= \
|
20
|
+
Layout.new(site, plugin_layouts, layout_file, from_plugin: true)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
17
24
|
@layouts
|
18
25
|
end
|
19
26
|
|
@@ -23,8 +30,8 @@ module Bridgetown
|
|
23
30
|
|
24
31
|
private
|
25
32
|
|
26
|
-
def layout_entries
|
27
|
-
entries_in
|
33
|
+
def layout_entries(dir = layout_directory)
|
34
|
+
entries_in dir
|
28
35
|
end
|
29
36
|
|
30
37
|
def entries_in(dir)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bridgetown
|
4
|
+
class PluginContentReader
|
5
|
+
attr_reader :site, :content_dir
|
6
|
+
|
7
|
+
def initialize(site, plugin_content_dir)
|
8
|
+
@site = site
|
9
|
+
@content_dir = plugin_content_dir
|
10
|
+
@content_files = Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def read
|
14
|
+
return unless content_dir
|
15
|
+
|
16
|
+
Find.find(content_dir) do |path|
|
17
|
+
next if File.directory?(path)
|
18
|
+
|
19
|
+
if File.symlink?(path)
|
20
|
+
Bridgetown.logger.warn "Plugin content reader:", "Ignored symlinked asset: #{path}"
|
21
|
+
else
|
22
|
+
read_content_file(path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_content_file(path)
|
28
|
+
dir = File.dirname(path.sub("#{content_dir}/", ""))
|
29
|
+
name = File.basename(path)
|
30
|
+
|
31
|
+
@content_files << if Utils.has_yaml_header?(path)
|
32
|
+
Bridgetown::Page.new(site, content_dir, dir, name, from_plugin: true)
|
33
|
+
else
|
34
|
+
Bridgetown::StaticFile.new(site, content_dir, "/#{dir}", name)
|
35
|
+
end
|
36
|
+
|
37
|
+
add_to(site.pages, Bridgetown::Page)
|
38
|
+
add_to(site.static_files, Bridgetown::StaticFile)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_to(content_type, klass)
|
42
|
+
existing_paths = content_type.map(&:relative_path).compact
|
43
|
+
@content_files.select { |item| item.is_a?(klass) }.each do |item|
|
44
|
+
content_type << item unless existing_paths.include?(item.relative_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -5,11 +5,16 @@ module Bridgetown
|
|
5
5
|
attr_reader :document, :site
|
6
6
|
attr_writer :layouts, :payload
|
7
7
|
|
8
|
+
class << self
|
9
|
+
attr_accessor :cached_partials
|
10
|
+
end
|
11
|
+
|
8
12
|
def initialize(site, document, site_payload = nil)
|
9
13
|
@site = site
|
10
14
|
@document = document
|
11
15
|
@payload = site_payload
|
12
16
|
@layouts = nil
|
17
|
+
self.class.cached_partials ||= {}
|
13
18
|
end
|
14
19
|
|
15
20
|
# Fetches the payload used in Liquid rendering.
|
@@ -48,7 +53,7 @@ module Bridgetown
|
|
48
53
|
|
49
54
|
# Prepare payload and render the document
|
50
55
|
#
|
51
|
-
# Returns
|
56
|
+
# Returns nothing
|
52
57
|
def run
|
53
58
|
Bridgetown.logger.debug "Rendering:", document.relative_path
|
54
59
|
|
@@ -57,10 +62,9 @@ module Bridgetown
|
|
57
62
|
assign_highlighter_options!
|
58
63
|
assign_layout_data!
|
59
64
|
|
60
|
-
Bridgetown.logger.debug "Pre-Render Hooks:", document.relative_path
|
61
65
|
document.trigger_hooks(:pre_render, payload)
|
62
|
-
|
63
|
-
|
66
|
+
document.output = render_document
|
67
|
+
document.trigger_hooks(:post_render)
|
64
68
|
end
|
65
69
|
|
66
70
|
# Render the document.
|
@@ -68,18 +72,14 @@ module Bridgetown
|
|
68
72
|
# Returns String rendered document output
|
69
73
|
# rubocop: disable Metrics/AbcSize
|
70
74
|
def render_document
|
71
|
-
|
72
|
-
registers: { site: site, page: payload["page"] },
|
73
|
-
strict_filters: liquid_options["strict_filters"],
|
74
|
-
strict_variables: liquid_options["strict_variables"],
|
75
|
-
}
|
75
|
+
liquid_context = provide_liquid_context
|
76
76
|
|
77
77
|
execute_inline_ruby!
|
78
78
|
|
79
79
|
output = document.content
|
80
80
|
if document.render_with_liquid?
|
81
81
|
Bridgetown.logger.debug "Rendering Liquid:", document.relative_path
|
82
|
-
output = render_liquid(output, payload,
|
82
|
+
output = render_liquid(output, payload, liquid_context, document.path)
|
83
83
|
end
|
84
84
|
|
85
85
|
Bridgetown.logger.debug "Rendering Markup:", document.relative_path
|
@@ -88,12 +88,24 @@ module Bridgetown
|
|
88
88
|
|
89
89
|
if document.place_in_layout?
|
90
90
|
Bridgetown.logger.debug "Rendering Layout:", document.relative_path
|
91
|
-
output = place_in_layouts(output, payload,
|
91
|
+
output = place_in_layouts(output, payload, liquid_context)
|
92
92
|
end
|
93
93
|
|
94
94
|
output
|
95
95
|
end
|
96
96
|
|
97
|
+
def provide_liquid_context
|
98
|
+
{
|
99
|
+
registers: {
|
100
|
+
site: site,
|
101
|
+
page: payload["page"],
|
102
|
+
cached_partials: self.class.cached_partials,
|
103
|
+
},
|
104
|
+
strict_filters: liquid_options["strict_filters"],
|
105
|
+
strict_variables: liquid_options["strict_variables"],
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
97
109
|
def execute_inline_ruby!
|
98
110
|
return unless site.config.should_execute_inline_ruby?
|
99
111
|
|
@@ -102,21 +114,21 @@ module Bridgetown
|
|
102
114
|
|
103
115
|
# rubocop: enable Metrics/AbcSize
|
104
116
|
|
105
|
-
# Render the given content with the payload and
|
117
|
+
# Render the given content with the payload and context
|
106
118
|
#
|
107
119
|
# content -
|
108
120
|
# payload -
|
109
|
-
#
|
121
|
+
# context -
|
110
122
|
# path - (optional) the path to the file, for use in ex
|
111
123
|
#
|
112
124
|
# Returns String the content, rendered by Liquid.
|
113
|
-
def render_liquid(content, payload,
|
125
|
+
def render_liquid(content, payload, liquid_context, path = nil)
|
114
126
|
template = site.liquid_renderer.file(path).parse(content)
|
115
127
|
template.warnings.each do |e|
|
116
128
|
Bridgetown.logger.warn "Liquid Warning:",
|
117
129
|
LiquidRenderer.format_error(e, path || document.relative_path)
|
118
130
|
end
|
119
|
-
template.render!(payload,
|
131
|
+
template.render!(payload, liquid_context)
|
120
132
|
# rubocop: disable Lint/RescueException
|
121
133
|
rescue Exception => e
|
122
134
|
Bridgetown.logger.error "Liquid Exception:",
|
@@ -130,15 +142,13 @@ module Bridgetown
|
|
130
142
|
# Returns String the converted content.
|
131
143
|
def convert(content)
|
132
144
|
converters.reduce(content) do |output, converter|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
raise e
|
141
|
-
end
|
145
|
+
converter.convert output
|
146
|
+
rescue StandardError => e
|
147
|
+
Bridgetown.logger.error "Conversion error:",
|
148
|
+
"#{converter.class} encountered an error while "\
|
149
|
+
"converting '#{document.relative_path}':"
|
150
|
+
Bridgetown.logger.error("", e.to_s)
|
151
|
+
raise e
|
142
152
|
end
|
143
153
|
end
|
144
154
|
|
@@ -154,7 +164,7 @@ module Bridgetown
|
|
154
164
|
# Render layouts and place document content inside.
|
155
165
|
#
|
156
166
|
# Returns String rendered content
|
157
|
-
def place_in_layouts(content, payload,
|
167
|
+
def place_in_layouts(content, payload, liquid_context)
|
158
168
|
output = content.dup
|
159
169
|
layout = layouts[document.data["layout"].to_s]
|
160
170
|
validate_layout(layout)
|
@@ -165,7 +175,7 @@ module Bridgetown
|
|
165
175
|
payload["layout"] = nil
|
166
176
|
|
167
177
|
while layout
|
168
|
-
output = render_layout(output, layout,
|
178
|
+
output = render_layout(output, layout, liquid_context)
|
169
179
|
add_regenerator_dependencies(layout)
|
170
180
|
|
171
181
|
next unless (layout = site.layouts[layout.data["layout"]])
|
@@ -192,14 +202,14 @@ module Bridgetown
|
|
192
202
|
# Render layout content into document.output
|
193
203
|
#
|
194
204
|
# Returns String rendered content
|
195
|
-
def render_layout(output, layout,
|
205
|
+
def render_layout(output, layout, liquid_context)
|
196
206
|
payload["content"] = output
|
197
207
|
payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
|
198
208
|
|
199
209
|
render_liquid(
|
200
210
|
layout.content,
|
201
211
|
payload,
|
202
|
-
|
212
|
+
liquid_context,
|
203
213
|
layout.path
|
204
214
|
)
|
205
215
|
end
|
data/lib/bridgetown-core/site.rb
CHANGED
@@ -2,125 +2,43 @@
|
|
2
2
|
|
3
3
|
module Bridgetown
|
4
4
|
class Site
|
5
|
-
|
5
|
+
require_all "bridgetown-core/concerns/site"
|
6
|
+
|
7
|
+
include Configurable
|
8
|
+
include Content
|
9
|
+
include Extensible
|
10
|
+
include Processable
|
11
|
+
include Renderable
|
12
|
+
include Writable
|
13
|
+
|
14
|
+
attr_reader :root_dir, :source, :dest, :cache_dir, :config,
|
15
|
+
:regenerator, :liquid_renderer, :components_load_paths,
|
16
|
+
:includes_load_paths
|
6
17
|
attr_accessor :layouts, :pages, :static_files,
|
7
18
|
:exclude, :include, :lsi, :highlighter, :permalink_style,
|
8
|
-
:time, :future, :unpublished, :
|
19
|
+
:time, :future, :unpublished, :limit_posts,
|
9
20
|
:keep_files, :baseurl, :data, :file_read_opts,
|
10
|
-
:plugin_manager
|
11
|
-
|
12
|
-
attr_accessor :converters, :generators, :reader
|
13
|
-
attr_reader :regenerator, :liquid_renderer, :components_load_paths,
|
14
|
-
:includes_load_paths
|
21
|
+
:plugin_manager, :converters, :generators, :reader
|
15
22
|
|
16
23
|
# Public: Initialize a new Site.
|
17
24
|
#
|
18
25
|
# config - A Hash containing site configuration details.
|
19
26
|
def initialize(config)
|
20
|
-
# Source and destination may not be changed after the site has been created.
|
21
|
-
@root_dir = File.expand_path(config["root_dir"]).freeze
|
22
|
-
@source = File.expand_path(config["source"]).freeze
|
23
|
-
@dest = File.expand_path(config["destination"]).freeze
|
24
|
-
|
25
27
|
self.config = config
|
26
28
|
|
27
|
-
@
|
29
|
+
@plugin_manager = PluginManager.new(self)
|
30
|
+
@cleaner = Cleaner.new(self)
|
28
31
|
@reader = Reader.new(self)
|
29
32
|
@regenerator = Regenerator.new(self)
|
30
33
|
@liquid_renderer = LiquidRenderer.new(self)
|
31
34
|
|
32
|
-
Bridgetown.sites << self
|
33
|
-
|
34
|
-
reset
|
35
|
-
setup
|
36
|
-
|
37
|
-
Bridgetown::Hooks.trigger :site, :after_init, self
|
38
|
-
end
|
39
|
-
|
40
|
-
# Public: Set the site's configuration. This handles side-effects caused by
|
41
|
-
# changing values in the configuration.
|
42
|
-
#
|
43
|
-
# config - a Bridgetown::Configuration, containing the new configuration.
|
44
|
-
#
|
45
|
-
# Returns the new configuration.
|
46
|
-
def config=(config)
|
47
|
-
@config = config.clone
|
48
|
-
|
49
|
-
%w(lsi highlighter baseurl exclude include future unpublished
|
50
|
-
limit_posts keep_files).each do |opt|
|
51
|
-
send("#{opt}=", config[opt])
|
52
|
-
end
|
53
|
-
|
54
|
-
configure_cache
|
55
|
-
configure_plugins
|
56
|
-
configure_component_paths
|
57
|
-
configure_include_paths
|
58
|
-
configure_file_read_opts
|
59
|
-
|
60
|
-
self.permalink_style = config["permalink"].to_sym
|
61
|
-
|
62
|
-
@config
|
63
|
-
end
|
64
|
-
|
65
|
-
# Public: Read, process, and write this Site to output.
|
66
|
-
#
|
67
|
-
# Returns nothing.
|
68
|
-
def process
|
69
|
-
reset
|
70
|
-
read
|
71
|
-
generate
|
72
|
-
render
|
73
|
-
cleanup
|
74
|
-
write
|
75
|
-
print_stats if config["profile"]
|
76
|
-
end
|
77
|
-
|
78
|
-
def print_stats
|
79
|
-
Bridgetown.logger.info @liquid_renderer.stats_table
|
80
|
-
end
|
81
|
-
|
82
|
-
# rubocop:disable Metrics/MethodLength
|
83
|
-
#
|
84
|
-
# Reset Site details.
|
85
|
-
#
|
86
|
-
# Returns nothing
|
87
|
-
def reset
|
88
|
-
self.time = if config["time"]
|
89
|
-
Utils.parse_date(config["time"].to_s, "Invalid time in bridgetown.config.yml.")
|
90
|
-
else
|
91
|
-
Time.now
|
92
|
-
end
|
93
|
-
self.layouts = {}
|
94
|
-
self.pages = []
|
95
|
-
self.static_files = []
|
96
|
-
self.data = {}
|
97
|
-
@post_attr_hash = {}
|
98
|
-
@site_data = nil
|
99
|
-
@collections = nil
|
100
|
-
@documents = nil
|
101
|
-
@docs_to_write = nil
|
102
|
-
@regenerator.clear_cache
|
103
|
-
@liquid_renderer.reset
|
104
|
-
@site_cleaner = nil
|
105
|
-
frontmatter_defaults.reset
|
106
|
-
|
107
|
-
raise ArgumentError, "limit_posts must be a non-negative number" if limit_posts.negative?
|
108
|
-
|
109
|
-
Bridgetown::Cache.clear_if_config_changed config
|
110
|
-
Bridgetown::Hooks.trigger :site, :after_reset, self
|
111
|
-
end
|
112
|
-
# rubocop:enable Metrics/MethodLength
|
113
|
-
|
114
|
-
# Load necessary libraries, plugins, converters, and generators.
|
115
|
-
#
|
116
|
-
# Returns nothing.
|
117
|
-
def setup
|
118
35
|
ensure_not_in_dest
|
119
36
|
|
120
|
-
|
37
|
+
Bridgetown.sites << self
|
38
|
+
Bridgetown::Hooks.trigger :site, :after_init, self
|
121
39
|
|
122
|
-
|
123
|
-
|
40
|
+
reset # Processable
|
41
|
+
setup # Extensible
|
124
42
|
end
|
125
43
|
|
126
44
|
# Check that the destination dir isn't the source dir or a directory
|
@@ -134,366 +52,5 @@ module Bridgetown
|
|
134
52
|
end
|
135
53
|
end
|
136
54
|
end
|
137
|
-
|
138
|
-
# The list of collections and their corresponding Bridgetown::Collection instances.
|
139
|
-
# If config['collections'] is set, a new instance is created
|
140
|
-
# for each item in the collection, a new hash is returned otherwise.
|
141
|
-
#
|
142
|
-
# Returns a Hash containing collection name-to-instance pairs.
|
143
|
-
def collections
|
144
|
-
@collections ||= collection_names.each_with_object({}) do |name, hsh|
|
145
|
-
hsh[name] = Bridgetown::Collection.new(self, name)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# The list of collection names.
|
150
|
-
#
|
151
|
-
# Returns an array of collection names from the configuration,
|
152
|
-
# or an empty array if the `collections` key is not set.
|
153
|
-
def collection_names
|
154
|
-
case config["collections"]
|
155
|
-
when Hash
|
156
|
-
config["collections"].keys
|
157
|
-
when Array
|
158
|
-
config["collections"]
|
159
|
-
when nil
|
160
|
-
[]
|
161
|
-
else
|
162
|
-
raise ArgumentError, "Your `collections` key must be a hash or an array."
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
# Read Site data from disk and load it into internal data structures.
|
167
|
-
#
|
168
|
-
# Returns nothing.
|
169
|
-
def read
|
170
|
-
reader.read
|
171
|
-
limit_posts!
|
172
|
-
Bridgetown::Hooks.trigger :site, :post_read, self
|
173
|
-
end
|
174
|
-
|
175
|
-
# Run each of the Generators.
|
176
|
-
#
|
177
|
-
# Returns nothing.
|
178
|
-
def generate
|
179
|
-
generators.each do |generator|
|
180
|
-
start = Time.now
|
181
|
-
generator.generate(self)
|
182
|
-
Bridgetown.logger.debug "Generating:",
|
183
|
-
"#{generator.class} finished in #{Time.now - start} seconds."
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Render the site to the destination.
|
188
|
-
#
|
189
|
-
# Returns nothing.
|
190
|
-
def render
|
191
|
-
payload = site_payload
|
192
|
-
|
193
|
-
Bridgetown::Hooks.trigger :site, :pre_render, self, payload
|
194
|
-
|
195
|
-
execute_inline_ruby_for_layouts!
|
196
|
-
|
197
|
-
render_docs(payload)
|
198
|
-
render_pages(payload)
|
199
|
-
|
200
|
-
Bridgetown::Hooks.trigger :site, :post_render, self, payload
|
201
|
-
end
|
202
|
-
|
203
|
-
# Remove orphaned files and empty directories in destination.
|
204
|
-
#
|
205
|
-
# Returns nothing.
|
206
|
-
def cleanup
|
207
|
-
site_cleaner.cleanup!
|
208
|
-
end
|
209
|
-
|
210
|
-
# Write static files, pages, and posts.
|
211
|
-
#
|
212
|
-
# Returns nothing.
|
213
|
-
def write
|
214
|
-
each_site_file do |item|
|
215
|
-
item.write(dest) if regenerator.regenerate?(item)
|
216
|
-
end
|
217
|
-
regenerator.write_metadata
|
218
|
-
Bridgetown::Hooks.trigger :site, :post_write, self
|
219
|
-
end
|
220
|
-
|
221
|
-
def posts
|
222
|
-
collections["posts"] ||= Collection.new(self, "posts")
|
223
|
-
end
|
224
|
-
|
225
|
-
# Construct a Hash of Posts indexed by the specified Post attribute.
|
226
|
-
#
|
227
|
-
# post_attr - The String name of the Post attribute.
|
228
|
-
#
|
229
|
-
# Examples
|
230
|
-
#
|
231
|
-
# post_attr_hash('categories')
|
232
|
-
# # => { 'tech' => [<Post A>, <Post B>],
|
233
|
-
# # 'ruby' => [<Post B>] }
|
234
|
-
#
|
235
|
-
# Returns the Hash: { attr => posts } where
|
236
|
-
# attr - One of the values for the requested attribute.
|
237
|
-
# posts - The Array of Posts with the given attr value.
|
238
|
-
def post_attr_hash(post_attr)
|
239
|
-
# Build a hash map based on the specified post attribute ( post attr =>
|
240
|
-
# array of posts ) then sort each array in reverse order.
|
241
|
-
@post_attr_hash[post_attr] ||= begin
|
242
|
-
hash = Hash.new { |h, key| h[key] = [] }
|
243
|
-
posts.docs.each do |p|
|
244
|
-
p.data[post_attr]&.each { |t| hash[t] << p }
|
245
|
-
end
|
246
|
-
hash.each_value { |posts| posts.sort!.reverse! }
|
247
|
-
hash
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
def tags
|
252
|
-
post_attr_hash("tags")
|
253
|
-
end
|
254
|
-
|
255
|
-
def categories
|
256
|
-
post_attr_hash("categories")
|
257
|
-
end
|
258
|
-
|
259
|
-
# Prepare site data for site payload. The method maintains backward compatibility
|
260
|
-
# if the key 'data' is already used in bridgetown.config.yml.
|
261
|
-
#
|
262
|
-
# Returns the Hash to be hooked to site.data.
|
263
|
-
def site_data
|
264
|
-
@site_data ||= (config["data"] || data)
|
265
|
-
end
|
266
|
-
|
267
|
-
def metadata
|
268
|
-
data["site_metadata"] || {}
|
269
|
-
end
|
270
|
-
|
271
|
-
# The Hash payload containing site-wide data.
|
272
|
-
#
|
273
|
-
# Returns the Hash: { "site" => data } where data is a Hash with keys:
|
274
|
-
# "time" - The Time as specified in the configuration or the
|
275
|
-
# current time if none was specified.
|
276
|
-
# "posts" - The Array of Posts, sorted chronologically by post date
|
277
|
-
# and then title.
|
278
|
-
# "pages" - The Array of all Pages.
|
279
|
-
# "html_pages" - The Array of HTML Pages.
|
280
|
-
# "categories" - The Hash of category values and Posts.
|
281
|
-
# See Site#post_attr_hash for type info.
|
282
|
-
# "tags" - The Hash of tag values and Posts.
|
283
|
-
# See Site#post_attr_hash for type info.
|
284
|
-
def site_payload
|
285
|
-
Drops::UnifiedPayloadDrop.new self
|
286
|
-
end
|
287
|
-
alias_method :to_liquid, :site_payload
|
288
|
-
|
289
|
-
# Get the implementation class for the given Converter.
|
290
|
-
# Returns the Converter instance implementing the given Converter.
|
291
|
-
# klass - The Class of the Converter to fetch.
|
292
|
-
def find_converter_instance(klass)
|
293
|
-
@find_converter_instance ||= {}
|
294
|
-
@find_converter_instance[klass] ||= begin
|
295
|
-
converters.find { |converter| converter.instance_of?(klass) } || \
|
296
|
-
raise("No Converters found for #{klass}")
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
# klass - class or module containing the subclasses.
|
301
|
-
# Returns array of instances of subclasses of parameter.
|
302
|
-
# Create array of instances of the subclasses of the class or module
|
303
|
-
# passed in as argument.
|
304
|
-
|
305
|
-
def instantiate_subclasses(klass)
|
306
|
-
klass.descendants.sort.map do |c|
|
307
|
-
c.new(config)
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
# Get the to be written documents
|
312
|
-
#
|
313
|
-
# Returns an Array of Documents which should be written
|
314
|
-
def docs_to_write
|
315
|
-
documents.select(&:write?)
|
316
|
-
end
|
317
|
-
|
318
|
-
# Get all the documents
|
319
|
-
#
|
320
|
-
# Returns an Array of all Documents
|
321
|
-
def documents
|
322
|
-
collections.each_with_object(Set.new) do |(_, collection), set|
|
323
|
-
set.merge(collection.docs).merge(collection.files)
|
324
|
-
end.to_a
|
325
|
-
end
|
326
|
-
|
327
|
-
def each_site_file
|
328
|
-
%w(pages static_files docs_to_write).each do |type|
|
329
|
-
send(type).each do |item|
|
330
|
-
yield item
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
# Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
|
336
|
-
# if it doesn't already exist.
|
337
|
-
#
|
338
|
-
# Returns The FrontmatterDefaults
|
339
|
-
def frontmatter_defaults
|
340
|
-
@frontmatter_defaults ||= FrontmatterDefaults.new(self)
|
341
|
-
end
|
342
|
-
|
343
|
-
# Whether to perform a full rebuild without incremental regeneration
|
344
|
-
#
|
345
|
-
# Returns a Boolean: true for a full rebuild, false for normal build
|
346
|
-
def incremental?(override = {})
|
347
|
-
override["incremental"] || config["incremental"]
|
348
|
-
end
|
349
|
-
|
350
|
-
# Returns the publisher or creates a new publisher if it doesn't
|
351
|
-
# already exist.
|
352
|
-
#
|
353
|
-
# Returns The Publisher
|
354
|
-
def publisher
|
355
|
-
@publisher ||= Publisher.new(self)
|
356
|
-
end
|
357
|
-
|
358
|
-
# Public: Prefix a given path with the root directory.
|
359
|
-
#
|
360
|
-
# paths - (optional) path elements to a file or directory within the
|
361
|
-
# root directory
|
362
|
-
#
|
363
|
-
# Returns a path which is prefixed with the root_dir directory.
|
364
|
-
def in_root_dir(*paths)
|
365
|
-
paths.reduce(root_dir) do |base, path|
|
366
|
-
Bridgetown.sanitized_path(base, path)
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
# Public: Prefix a given path with the source directory.
|
371
|
-
#
|
372
|
-
# paths - (optional) path elements to a file or directory within the
|
373
|
-
# source directory
|
374
|
-
#
|
375
|
-
# Returns a path which is prefixed with the source directory.
|
376
|
-
def in_source_dir(*paths)
|
377
|
-
paths.reduce(source) do |base, path|
|
378
|
-
Bridgetown.sanitized_path(base, path)
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
# Public: Prefix a given path with the destination directory.
|
383
|
-
#
|
384
|
-
# paths - (optional) path elements to a file or directory within the
|
385
|
-
# destination directory
|
386
|
-
#
|
387
|
-
# Returns a path which is prefixed with the destination directory.
|
388
|
-
def in_dest_dir(*paths)
|
389
|
-
paths.reduce(dest) do |base, path|
|
390
|
-
Bridgetown.sanitized_path(base, path)
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
# Public: Prefix a given path with the cache directory.
|
395
|
-
#
|
396
|
-
# paths - (optional) path elements to a file or directory within the
|
397
|
-
# cache directory
|
398
|
-
#
|
399
|
-
# Returns a path which is prefixed with the cache directory.
|
400
|
-
def in_cache_dir(*paths)
|
401
|
-
paths.reduce(cache_dir) do |base, path|
|
402
|
-
Bridgetown.sanitized_path(base, path)
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
# Public: The full path to the directory that houses all the collections registered
|
407
|
-
# with the current site.
|
408
|
-
#
|
409
|
-
# Returns the source directory or the absolute path to the custom collections_dir
|
410
|
-
def collections_path
|
411
|
-
dir_str = config["collections_dir"]
|
412
|
-
@collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str)
|
413
|
-
end
|
414
|
-
|
415
|
-
private
|
416
|
-
|
417
|
-
# Limits the current posts; removes the posts which exceed the limit_posts
|
418
|
-
#
|
419
|
-
# Returns nothing
|
420
|
-
def limit_posts!
|
421
|
-
if limit_posts.positive?
|
422
|
-
limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
|
423
|
-
posts.docs = posts.docs[-limit, limit]
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
# Returns the Cleaner or creates a new Cleaner if it doesn't
|
428
|
-
# already exist.
|
429
|
-
#
|
430
|
-
# Returns The Cleaner
|
431
|
-
def site_cleaner
|
432
|
-
@site_cleaner ||= Cleaner.new(self)
|
433
|
-
end
|
434
|
-
|
435
|
-
# Disable Marshaling cache to disk in Safe Mode
|
436
|
-
def configure_cache
|
437
|
-
Bridgetown::Cache.cache_dir = in_root_dir(config["cache_dir"], "Bridgetown/Cache")
|
438
|
-
Bridgetown::Cache.disable_disk_cache! if config["disable_disk_cache"]
|
439
|
-
end
|
440
|
-
|
441
|
-
def configure_plugins
|
442
|
-
self.plugin_manager = Bridgetown::PluginManager.new(self)
|
443
|
-
self.plugins = plugin_manager.plugins_path
|
444
|
-
end
|
445
|
-
|
446
|
-
def configure_component_paths
|
447
|
-
@components_load_paths = config["components_dir"].then do |dir|
|
448
|
-
dir.is_a?(Array) ? dir : [dir]
|
449
|
-
end
|
450
|
-
@components_load_paths.map! do |dir|
|
451
|
-
if !!(dir =~ %r!^\.\.?\/!)
|
452
|
-
# allow ./dir or ../../dir type options
|
453
|
-
File.expand_path(dir.to_s, root_dir)
|
454
|
-
else
|
455
|
-
in_source_dir(dir.to_s)
|
456
|
-
end
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
def configure_include_paths
|
461
|
-
@includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
|
462
|
-
end
|
463
|
-
|
464
|
-
def configure_file_read_opts
|
465
|
-
self.file_read_opts = {}
|
466
|
-
file_read_opts[:encoding] = config["encoding"] if config["encoding"]
|
467
|
-
self.file_read_opts = Bridgetown::Utils.merged_file_read_opts(self, {})
|
468
|
-
end
|
469
|
-
|
470
|
-
def execute_inline_ruby_for_layouts!
|
471
|
-
return unless config.should_execute_inline_ruby?
|
472
|
-
|
473
|
-
layouts.each_value do |layout|
|
474
|
-
Bridgetown::Utils::RubyExec.search_data_for_ruby_code(layout, self)
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
def render_docs(payload)
|
479
|
-
collections.each_value do |collection|
|
480
|
-
collection.docs.each do |document|
|
481
|
-
render_regenerated(document, payload)
|
482
|
-
end
|
483
|
-
end
|
484
|
-
end
|
485
|
-
|
486
|
-
def render_pages(payload)
|
487
|
-
pages.each do |page|
|
488
|
-
render_regenerated(page, payload)
|
489
|
-
end
|
490
|
-
end
|
491
|
-
|
492
|
-
def render_regenerated(document, payload)
|
493
|
-
return unless regenerator.regenerate?(document)
|
494
|
-
|
495
|
-
document.output = Bridgetown::Renderer.new(self, document, payload).run
|
496
|
-
document.trigger_hooks(:post_render)
|
497
|
-
end
|
498
55
|
end
|
499
56
|
end
|