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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -1
  3. data/bin/bridgetown +9 -48
  4. data/bridgetown-core.gemspec +6 -2
  5. data/lib/bridgetown-core.rb +13 -3
  6. data/lib/bridgetown-core/cleaner.rb +1 -0
  7. data/lib/bridgetown-core/commands/apply.rb +73 -0
  8. data/lib/bridgetown-core/commands/base.rb +45 -0
  9. data/lib/bridgetown-core/commands/build.rb +91 -86
  10. data/lib/bridgetown-core/commands/clean.rb +30 -29
  11. data/lib/bridgetown-core/commands/concerns/actions.rb +123 -0
  12. data/lib/bridgetown-core/commands/concerns/build_options.rb +76 -0
  13. data/lib/bridgetown-core/commands/concerns/configuration_overridable.rb +18 -0
  14. data/lib/bridgetown-core/commands/concerns/summarizable.rb +13 -0
  15. data/lib/bridgetown-core/commands/console.rb +46 -39
  16. data/lib/bridgetown-core/commands/doctor.rb +126 -127
  17. data/lib/bridgetown-core/commands/new.rb +120 -158
  18. data/lib/bridgetown-core/commands/plugins.rb +206 -0
  19. data/lib/bridgetown-core/commands/registrations.rb +16 -0
  20. data/lib/bridgetown-core/commands/serve.rb +214 -215
  21. data/lib/bridgetown-core/{convertible.rb → concerns/convertible.rb} +3 -6
  22. data/lib/bridgetown-core/concerns/site/configurable.rb +153 -0
  23. data/lib/bridgetown-core/concerns/site/content.rb +111 -0
  24. data/lib/bridgetown-core/concerns/site/extensible.rb +56 -0
  25. data/lib/bridgetown-core/concerns/site/processable.rb +74 -0
  26. data/lib/bridgetown-core/concerns/site/renderable.rb +49 -0
  27. data/lib/bridgetown-core/concerns/site/writable.rb +31 -0
  28. data/lib/bridgetown-core/configuration.rb +2 -9
  29. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +0 -3
  30. data/lib/bridgetown-core/document.rb +1 -1
  31. data/lib/bridgetown-core/drops/page_drop.rb +1 -1
  32. data/lib/bridgetown-core/drops/site_drop.rb +1 -1
  33. data/lib/bridgetown-core/excerpt.rb +4 -1
  34. data/lib/bridgetown-core/external.rb +17 -21
  35. data/lib/bridgetown-core/filters.rb +10 -0
  36. data/lib/bridgetown-core/generators/prototype_generator.rb +3 -1
  37. data/lib/bridgetown-core/hooks.rb +62 -62
  38. data/lib/bridgetown-core/layout.rb +10 -4
  39. data/lib/bridgetown-core/liquid_renderer.rb +1 -0
  40. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -4
  41. data/lib/bridgetown-core/liquid_renderer/file_system.rb +3 -1
  42. data/lib/bridgetown-core/page.rb +11 -19
  43. data/lib/bridgetown-core/plugin.rb +2 -0
  44. data/lib/bridgetown-core/plugin_manager.rb +88 -21
  45. data/lib/bridgetown-core/reader.rb +5 -0
  46. data/lib/bridgetown-core/readers/data_reader.rb +5 -2
  47. data/lib/bridgetown-core/readers/layout_reader.rb +9 -2
  48. data/lib/bridgetown-core/readers/plugin_content_reader.rb +48 -0
  49. data/lib/bridgetown-core/renderer.rb +38 -28
  50. data/lib/bridgetown-core/site.rb +20 -463
  51. data/lib/bridgetown-core/tags/include.rb +12 -0
  52. data/lib/bridgetown-core/tags/render_content.rb +29 -16
  53. data/lib/bridgetown-core/tags/with.rb +15 -0
  54. data/lib/bridgetown-core/utils.rb +45 -27
  55. data/lib/bridgetown-core/utils/ruby_exec.rb +1 -4
  56. data/lib/bridgetown-core/version.rb +2 -2
  57. data/lib/bridgetown-core/watcher.rb +21 -10
  58. data/lib/site_template/Gemfile.erb +19 -0
  59. data/lib/site_template/package.json +1 -0
  60. data/lib/site_template/plugins/{.keep → builders/.keep} +0 -0
  61. data/lib/site_template/plugins/site_builder.rb +4 -0
  62. data/lib/site_template/src/_components/footer.html +3 -0
  63. data/lib/site_template/src/_components/head.html +9 -0
  64. data/lib/site_template/src/{_includes → _components}/navbar.html +1 -0
  65. data/lib/site_template/src/_layouts/default.html +3 -3
  66. data/lib/site_template/src/posts.md +15 -0
  67. data/lib/site_template/start.js +1 -1
  68. data/lib/site_template/webpack.config.js +3 -3
  69. metadata +90 -18
  70. data/lib/bridgetown-core/command.rb +0 -106
  71. data/lib/bridgetown-core/commands/help.rb +0 -34
  72. data/lib/site_template/src/_components/.keep +0 -0
  73. data/lib/site_template/src/_includes/footer.html +0 -3
  74. data/lib/site_template/src/_includes/head.html +0 -9
@@ -10,6 +10,8 @@ module Bridgetown
10
10
  high: 10,
11
11
  }.freeze
12
12
 
13
+ SourceManifest = Struct.new(:origin, :components, :content, :layouts, keyword_init: true)
14
+
13
15
  #
14
16
 
15
17
  def self.inherited(const)
@@ -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.setup
28
- required_gems = Bundler.require(:bridgetown_plugins)
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
- message = "Required #{required_gems.map(&:name).join(", ")}"
31
- Bridgetown.logger.debug("PluginManager:", message)
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
- next unless loaded_gem.to_spec&.metadata&.dig("yarn-add")
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 #{yarn_add_dependency.join("@")}"
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
- Bridgetown::External.require_with_graceful_fail(plugin_files)
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(path, data[sanitize_filename(entry)] = {})
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 layout_directory
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 String rendered document output
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
- render_document
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
- info = {
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, info, document.path)
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, info)
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 info
117
+ # Render the given content with the payload and context
106
118
  #
107
119
  # content -
108
120
  # payload -
109
- # info -
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, info, path = nil)
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, info)
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
- begin
134
- converter.convert output
135
- rescue StandardError => e
136
- Bridgetown.logger.error "Conversion error:",
137
- "#{converter.class} encountered an error while "\
138
- "converting '#{document.relative_path}':"
139
- Bridgetown.logger.error("", e.to_s)
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, info)
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, info)
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, info)
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
- info,
212
+ liquid_context,
203
213
  layout.path
204
214
  )
205
215
  end
@@ -2,125 +2,43 @@
2
2
 
3
3
  module Bridgetown
4
4
  class Site
5
- attr_reader :root_dir, :source, :dest, :cache_dir, :config
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, :plugins, :limit_posts,
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
- @cache_dir = in_root_dir(config["cache_dir"])
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
- plugin_manager.conscientious_require
37
+ Bridgetown.sites << self
38
+ Bridgetown::Hooks.trigger :site, :after_init, self
121
39
 
122
- self.converters = instantiate_subclasses(Bridgetown::Converter)
123
- self.generators = instantiate_subclasses(Bridgetown::Generator)
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